<?php

declare(strict_types=1);

namespace App\Framework\Svg\Builder;

use App\Framework\Core\ValueObjects\Dimensions;
use App\Framework\Filesystem\FileStorage;
use App\Framework\Svg\Elements\CircleElement;
use App\Framework\Svg\Elements\GroupElement;
use App\Framework\Svg\Elements\LineElement;
use App\Framework\Svg\Elements\RectElement;
use App\Framework\Svg\Elements\SvgElement;
use App\Framework\Svg\Elements\TextElement;
use App\Framework\Svg\ValueObjects\Geometry\Position;
use App\Framework\Svg\ValueObjects\Geometry\Radius;
use App\Framework\Svg\ValueObjects\Geometry\ViewBox;
use App\Framework\Svg\ValueObjects\Styling\Fill;
use App\Framework\Svg\ValueObjects\Styling\Stroke;
use App\Framework\Svg\ValueObjects\Text\TextContent;
use App\Framework\Svg\ValueObjects\Text\TextStyle;
use App\Framework\Svg\ValueObjects\Transform\Transform;

/**
 * Mutable builder for creating SVG documents
 * Uses fluent interface for convenient API
 */
final class SvgCanvas
{
    /** @var array<SvgElement> */
    private array $elements = [];

    public function __construct(
        private readonly Dimensions $dimensions,
        private readonly ?ViewBox $viewBox = null,
        private ?string $title = null,
        private ?string $description = null,
        private readonly ?FileStorage $fileStorage = null
    ) {
    }

    /**
     * Add rectangle to canvas
     */
    public function rect(
        Position $position,
        Dimensions $dimensions,
        Fill $fill,
        ?Stroke $stroke = null,
        ?Radius $rx = null,
        ?Radius $ry = null,
        ?Transform $transform = null,
        ?string $id = null,
        ?string $class = null
    ): self {
        $this->elements[] = new RectElement(
            $position,
            $dimensions,
            $fill,
            $stroke,
            $rx,
            $ry,
            $transform,
            $id,
            $class
        );

        return $this;
    }

    /**
     * Add circle to canvas
     */
    public function circle(
        Position $center,
        Radius $radius,
        Fill $fill,
        ?Stroke $stroke = null,
        ?Transform $transform = null,
        ?string $id = null,
        ?string $class = null
    ): self {
        $this->elements[] = new CircleElement(
            $center,
            $radius,
            $fill,
            $stroke,
            $transform,
            $id,
            $class
        );

        return $this;
    }

    /**
     * Add line to canvas
     */
    public function line(
        Position $start,
        Position $end,
        Stroke $stroke,
        ?Transform $transform = null,
        ?string $id = null,
        ?string $class = null
    ): self {
        $this->elements[] = new LineElement(
            $start,
            $end,
            $stroke,
            $transform,
            $id,
            $class
        );

        return $this;
    }

    /**
     * Add text to canvas
     */
    public function text(
        string $content,
        Position $position,
        TextStyle $style,
        ?Transform $transform = null,
        ?string $id = null,
        ?string $class = null
    ): self {
        $this->elements[] = new TextElement(
            new TextContent($content),
            $position,
            $style,
            $transform,
            $id,
            $class
        );

        return $this;
    }

    /**
     * Add group to canvas
     */
    public function group(
        array $children,
        ?Transform $transform = null,
        ?string $id = null,
        ?string $class = null
    ): self {
        $this->elements[] = new GroupElement(
            $children,
            $transform,
            $id,
            $class
        );

        return $this;
    }

    /**
     * Add custom element to canvas
     */
    public function element(SvgElement $element): self
    {
        $this->elements[] = $element;

        return $this;
    }

    /**
     * Set title for accessibility
     */
    public function withTitle(string $title): self
    {
        $this->title = $title;

        return $this;
    }

    /**
     * Set description for accessibility
     */
    public function withDescription(string $description): self
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Get all elements
     */
    public function getElements(): array
    {
        return $this->elements;
    }

    /**
     * Clear all elements
     */
    public function clear(): self
    {
        $this->elements = [];

        return $this;
    }

    /**
     * Get element count
     */
    public function count(): int
    {
        return count($this->elements);
    }

    /**
     * Render complete SVG document
     */
    public function toSvg(): string
    {
        $svgAttributes = [
            'width' => (string) $this->dimensions->width,
            'height' => (string) $this->dimensions->height,
            'xmlns' => 'http://www.w3.org/2000/svg',
        ];

        if ($this->viewBox !== null) {
            $svgAttributes['viewBox'] = $this->viewBox->toSvgAttribute();
        }

        $attributeString = $this->buildAttributeString($svgAttributes);

        $svg = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
        $svg .= "<svg {$attributeString}>\n";

        // Add accessibility elements
        if ($this->title !== null) {
            $svg .= "  <title>" . htmlspecialchars($this->title, ENT_XML1, 'UTF-8') . "</title>\n";
        }

        if ($this->description !== null) {
            $svg .= "  <desc>" . htmlspecialchars($this->description, ENT_XML1, 'UTF-8') . "</desc>\n";
        }

        // Add all elements
        foreach ($this->elements as $element) {
            $svg .= "  " . $element->toSvg() . "\n";
        }

        $svg .= "</svg>";

        return $svg;
    }

    /**
     * Render to inline SVG (without XML declaration)
     */
    public function toInlineSvg(): string
    {
        $svg = $this->toSvg();

        // Remove XML declaration
        return preg_replace('/<\?xml[^?]*\?>\s*/', '', $svg) ?? $svg;
    }

    /**
     * Export to file using Framework's FileStorage
     */
    public function toFile(string $filepath): void
    {
        $storage = $this->fileStorage ?? new FileStorage();
        $storage->put($filepath, $this->toSvg());
    }

    private function buildAttributeString(array $attributes): string
    {
        $parts = [];
        foreach ($attributes as $key => $value) {
            $parts[] = sprintf('%s="%s"', $key, htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'));
        }

        return implode(' ', $parts);
    }
}
