<?php
// Silence may be golden, but sometimes words paint the canvas of understanding.

namespace Plugli\Linksy\SearchAndReplace\Admin\Partials\SearchAndReplace\Helpers;

use Linksy\SearchAndReplace\Plugli\Standard\Helpers\Arr;
use Linksy\SearchAndReplace\Plugli\Standard\Helpers\Database\Database;
use Linksy\SearchAndReplace\Plugli\Standard\Helpers\Links;
use Linksy\SearchAndReplace\Plugli\Standard\Helpers\Str;

use Plugli\Linksy\SearchAndReplace\Helpers\Post\Post;

class LinkReplace extends Helpers
{
    protected $search_type;

    public function get($search_mode, $filter_query, $options = [])
    {
        $links = Database::table("linksy_links");

        $this->search_type = $search_mode;

        // perform database query based on search mode
        if ($search_mode === 'EXACT') {
            $links = $links->where('raw_url', $this->search);
        } elseif (in_array($search_mode, ['SELF_REFERENTIAL', 'TWO_WAY'])) {
            $links = $links->where('to_post_id', '>', '0');
        } elseif ($search_mode === 'BROKEN_LINKS') {
            $links = $links->where(
                function ($q) {
                    $media_exts = array_values(Links::MEDIA_EXTS);

                    foreach ($media_exts as $ext) {
                        $q = $q->whereNotLike("LOWER(raw_url)", $ext, "%", "");
                    }

                    return $q;
                }
            )->where('is_broken', '1');
        }

        if ($search_mode === 'CONTAINS') {
            $links = $links->whereLike('raw_url', $this->search);
        }
        
        $links = $links->get();

        // group links based on search mode
        if ($search_mode === 'TWO_WAY') {
            $data = $this->groupByTwoWay($links);
        } else {
            $data = $this->groupByOneWay($links);
        }

        // handle additional purging rules based on the search mode
        switch ($search_mode) {
            case 'SELF_REFERENTIAL':
                foreach ($data as $url => &$entry) {
                    $entry['sources'] = array_values(Arr::filter(
                        $entry['sources'],
                        function ($source) {
                            return isset($source['post_id'], $source['to_post_id']) && $source['post_id'] === $source['to_post_id'];
                        },
                        true
                    ));
                
                    if (empty($entry['sources'])) {
                        unset($data[$url]);
                    }
                }
                unset($entry);
                break;
        }

        if (!empty($this->filters['search_mode'])) {
            return $this->filter($data);
        }

        return $data;
    }

    public function filter($data = [], $query = '')
    {
        $filteredData = $data;

        foreach ($this->filters['search_mode'] as $mode) {
            $this->filter_mode = $mode;

            switch ($mode) {
                case 'anchor-is':
                    $query = $this->filters['anchor'] ?? '';
                    break;
                case 'source-is':
                    $query = $this->filters['source'] ?? '';
                    break;
                default:
                    $query = $this->filters[$mode] ?? '';
                    break;
            }

            $newFiltered = [];

            foreach ($filteredData as $item) {
                $result = $this->filterByMode($item, $query);

                if (!empty($result)) {
                    $newFiltered[] = $result;
                }
            }

            $filteredData = $newFiltered;
        }

        return $filteredData;
    }

    protected function filterByMode($item, $query)
    {
        if ($query === '') {
            return $item;
        }

        $queryLower = strtolower($query);

        $filterConditionContains = function ($anchor) use ($queryLower) {
            return isset($anchor['anchor']) && Str::contains(strtolower($anchor['anchor']), $queryLower);
        };
        $filterConditionExact = function ($anchor) use ($queryLower) {
            return isset($anchor['anchor']) && strtolower($anchor['anchor']) === $queryLower;
        };

        if (in_array($this->filter_mode, ['anchor', 'anchor-is'])) {
            $isExact = ($this->filter_mode === 'anchor-is');
            $filterFunction = $isExact ? $filterConditionExact : $filterConditionContains;

            if ($this->search_type === 'TWO_WAY') {
                $matchingValues = [];

                foreach ($item as $value) {
                    if (isset($value['anchors']) && is_array($value['anchors'])) {
                        $filteredAnchors = Arr::filter($value['anchors'], $filterFunction);

                        if (!empty($filteredAnchors)) {
                            $value['anchors'] = array_values($filteredAnchors);
                            $matchingValues[] = $value;
                        }
                    }
                }

                return !empty($matchingValues) ? $matchingValues : false;
            } else {
                $matchingSources = [];

                foreach ($item['sources'] as $source) {
                    if (isset($source['anchors']) && is_array($source['anchors'])) {
                        $filteredAnchors = Arr::filter($source['anchors'], $filterFunction);

                        if (!empty($filteredAnchors)) {
                            $source['anchors'] = array_values($filteredAnchors);
                            $matchingSources[] = $source;
                        }
                    }
                }

                if (!empty($matchingSources)) {
                    $item['sources'] = $matchingSources;
                    return $item;
                }

                return false;
            }
        }

        if (in_array($this->filter_mode, ['source', 'source-is'])) {
            $isExact = ($this->filter_mode === 'source-is');
            $filterSourceFunction = function ($source) use ($queryLower, $isExact) {
                return isset($source['source']) &&
                    ($isExact ? strtolower($source['source']) === $queryLower
                                : Str::contains(strtolower($source['source']), $queryLower));
            };

            $matchingSources = Arr::filter($item['sources'], $filterSourceFunction);

            if (!empty($matchingSources)) {
                $item['sources'] = array_values($matchingSources);
                return $item;
            }

            return false;
        }

        if ($this->filter_mode === 'link') {
            return Str::contains(strtolower($item['link'] ?? ''), $queryLower) ? $item : false;
        }

        return false;
    }

    private function getLinkOccurrence($link, $two_way = false)
    {
        $post = new Post($link->post_id);

        $linkInfo = [
            'source' => $link->post_title,
            'post_id' => $link->post_id,
            'to_post_id' => $link->to_post_id,
            'link' => $link->clean_url,
            'view_link' => $post->get_link(),
            'edit_link' => $post->get_edit_link(),
            'anchors' => [],
        ];

        if ($two_way) {
            $occurrence = $linkInfo;
            $occurrence['anchors'][] = [
                'anchor' => $link->anchor,
                'score' => $link->score,
            ];
        } else {
            $occurrence = [
                'link' => $link->clean_url,
                'sources' => [$linkInfo],
            ];
        }

        return $occurrence;
    }

    private function groupByOneWay($links = [])
    {
        $data = [];

        foreach ($links as $link) {
            $key = $link->clean_url;

            if (!isset($data[$key])) {
                $data[$key] = $this->getLinkOccurrence($link);
                
                $data[$key]['sources'][0]['anchors'][] = [
                    'anchor' => $link->anchor,
                    'score' => $link->score
                ];
            } else {
                $found_source = false;
                foreach ($data[$key]['sources'] as &$source) {
                    if ($source['post_id'] === $link->post_id) {
                        $source['anchors'][] = [
                            'anchor' => $link->anchor,
                            'score' => $link->score
                        ];
                        $found_source = true;
                        break;
                    }
                }

                if (!$found_source) {
                    $post = new Post($link->post_id);
                    $data[$key]['sources'][] = [
                        'source' => $link->post_title,
                        'post_id' => $link->post_id,
                        'to_post_id' => $link->to_post_id,
                        'view_link' => $post->get_link(),
                        'edit_link' => $post->get_edit_link(),
                        'anchors' => [
                            [
                                'anchor' => $link->anchor,
                                'score' => $link->score
                            ]
                        ]
                    ];
                }
            }
        }

        return $data;
    }

    private function groupByTwoWay($links = [])
    {
        $data = [];

        foreach ($links as $link) {
            $to_post_id = $link->to_post_id;
            $post_id = $link->post_id;
            $anchor = $link->anchor;

            if ($to_post_id == $post_id) {
                continue;
            }

            if (isset($data[$post_id])) {
                if ($data[$post_id][0]['post_id'] == $to_post_id) {
                    if (count($data[$post_id]) < 2) {
                        $data[$post_id][] = $this->getLinkOccurrence($link, true);
                    } else {
                        $data[$post_id][1]['anchors'][] = [
                            'anchor' => $anchor,
                            'score' => $link->score
                        ];
                    }
                } else {
                    if ($data[$post_id][0]['post_id'] == $post_id && $data[$post_id][0]['link'] == $link->clean_url) {
                        $data[$post_id][0]['anchors'][] = [
                            'anchor' => $anchor,
                            'score' => $link->score
                        ];
                    }
                }
            } else {
                if (!isset($data[$to_post_id])) {
                    $data[$to_post_id] = [$this->getLinkOccurrence($link, true)];
                } else {
                    if ($post_id == $data[$to_post_id][0]['post_id'] && $data[$to_post_id][0]['link'] == $link->clean_url) {
                        $data[$to_post_id][0]['anchors'][] = [
                            'anchor' => $anchor,
                            'score' => $link->score
                        ];
                    }
                }
            }
        }

        return Arr::filter(
            $data,
            function ($item) {
                return count($item) === 2;
            },
            true
        );
    }
}
