<?php
namespace Crawl\Services\Crawler;

use DOMDocument;
use DOMXPath;

class HtmlParser
{
    public function parse(string $html, string $url, UrlNormalizer $normalizer): array
    {
        $result = [
            'title' => null,
            'title_length' => 0,
            'meta_description' => null,
            'meta_description_length' => 0,
            'h1' => null,
            'h2_list' => [],
            'h3_list' => [],
            'canonical_url' => null,
            'meta_robots' => null,
            'json_ld_schema' => [],
            'links' => [],
            'images' => [],
            'visible_text' => '',
        ];

        if (trim($html) === '') {
            return $result;
        }

        $dom = new DOMDocument();
        libxml_use_internal_errors(true);
        $loaded = $dom->loadHTML($html);
        libxml_clear_errors();

        if (!$loaded) {
            return $result;
        }

        $xpath = new DOMXPath($dom);

        $titleNode = $xpath->query('//title')->item(0);
        if ($titleNode) {
            $result['title'] = trim($titleNode->textContent);
            $result['title_length'] = mb_strlen($result['title']);
        }

        $metaDesc = $xpath->query('//meta[@name="description"]')->item(0);
        if ($metaDesc) {
            $desc = trim((string)$metaDesc->getAttribute('content'));
            $result['meta_description'] = $desc;
            $result['meta_description_length'] = mb_strlen($desc);
        }

        $metaRobots = $xpath->query('//meta[@name="robots"]')->item(0);
        if ($metaRobots) {
            $result['meta_robots'] = strtolower(trim((string)$metaRobots->getAttribute('content')));
        }

        $canonical = $xpath->query('//link[@rel="canonical"]')->item(0);
        if ($canonical) {
            $canon = $normalizer->toAbsolute($url, (string)$canonical->getAttribute('href'));
            $result['canonical_url'] = $canon;
        }

        $h1Node = $xpath->query('//h1')->item(0);
        if ($h1Node) {
            $result['h1'] = trim($h1Node->textContent);
        }

        foreach ($xpath->query('//h2') as $node) {
            $text = trim($node->textContent);
            if ($text !== '') {
                $result['h2_list'][] = $text;
            }
        }

        foreach ($xpath->query('//h3') as $node) {
            $text = trim($node->textContent);
            if ($text !== '') {
                $result['h3_list'][] = $text;
            }
        }

        foreach ($xpath->query('//script[@type="application/ld+json"]') as $node) {
            $json = trim($node->textContent);
            if ($json !== '') {
                $result['json_ld_schema'][] = $json;
            }
        }

        foreach ($xpath->query('//a[@href]') as $a) {
            $href = (string)$a->getAttribute('href');
            $abs = $normalizer->toAbsolute($url, $href);
            if ($abs === null) {
                continue;
            }

            $result['links'][] = [
                'to_url'       => $abs,
                'anchor_text'  => trim($a->textContent),
                'rel'          => strtolower((string)$a->getAttribute('rel')),
                'link_context' => $this->detectLinkContext($a),
            ];
        }

        foreach ($xpath->query('//img[@src]') as $img) {
            $src = (string)$img->getAttribute('src');
            $abs = $normalizer->toAbsolute($url, $src);
            if ($abs === null) {
                continue;
            }

            $alt = trim((string)$img->getAttribute('alt'));
            $result['images'][] = [
                'image_url' => $abs,
                'alt_text' => $alt,
                'is_missing_alt' => $alt === '',
            ];
        }

        foreach ($xpath->query('//script|//style|//noscript') as $node) {
            $node->parentNode?->removeChild($node);
        }

        $body = $xpath->query('//body')->item(0);
        $text = $body ? trim(preg_replace('/\s+/', ' ', $body->textContent) ?? '') : '';
        $result['visible_text'] = $text;

        return $result;
    }

    private function detectLinkContext(\DOMNode $node): string
    {
        $parent = $node->parentNode;
        while ($parent !== null && $parent->nodeType === XML_ELEMENT_NODE) {
            $tag   = strtolower($parent->nodeName);
            $id    = strtolower($parent instanceof \DOMElement ? ($parent->getAttribute('id')    ?? '') : '');
            $cls   = strtolower($parent instanceof \DOMElement ? ($parent->getAttribute('class') ?? '') : '');
            $role  = strtolower($parent instanceof \DOMElement ? ($parent->getAttribute('role')  ?? '') : '');

            if ($tag === 'header' || str_contains($id, 'header') || str_contains($cls, 'header') || str_contains($id, 'topbar') || str_contains($cls, 'topbar')) {
                return 'Header';
            }
            if ($tag === 'footer' || str_contains($id, 'footer') || str_contains($cls, 'footer')) {
                return 'Footer';
            }
            if ($tag === 'nav' || $role === 'navigation' || str_contains($cls, 'navbar') || str_contains($cls, 'nav-') || str_contains($id, 'menu') || str_contains($cls, 'menu')) {
                return 'Navigation';
            }
            if ($tag === 'aside' || $role === 'complementary' || str_contains($id, 'sidebar') || str_contains($cls, 'sidebar') || str_contains($id, 'side-') || str_contains($cls, 'side-')) {
                return 'Sidebar';
            }
            if ($tag === 'button' || str_contains($cls, ' btn') || str_contains($cls, 'btn-') || str_contains($cls, 'button')) {
                return 'Button';
            }
            if ($tag === 'main' || $role === 'main' || str_contains($id, 'content') || str_contains($cls, 'content') || str_contains($id, 'main') || str_contains($cls, 'main')) {
                return 'Content';
            }

            $parent = $parent->parentNode;
        }
        return 'Body';
    }
}
