using System.Text.Json;
using System.Text.Json.Serialization;
using TraceCad.Core.Geometry;
using TraceCad.Core.Model;

namespace TraceCad.Core.Serialization;

public static class SketchDocumentSerializer
{
    private static readonly JsonSerializerOptions Options = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        WriteIndented = true,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    };

    public static string Serialize(SketchDocument document)
    {
        return JsonSerializer.Serialize(ToDto(document), Options);
    }

    public static SketchDocument Deserialize(string json)
    {
        var dto = JsonSerializer.Deserialize<DocumentDto>(json, Options)
                  ?? throw new InvalidOperationException("Project file is empty or invalid.");
        return FromDto(dto);
    }

    private static DocumentDto ToDto(SketchDocument document) =>
        new(
            document.Version,
            document.Units,
            new DocumentSizeDto(document.Width, document.Height),
            document.Reference,
            document.Layers.ToList(),
            document.Entities.Select(ToDto).ToList(),
            document.Constraints.ToList());

    private static SketchDocument FromDto(DocumentDto dto)
    {
        var document = new SketchDocument
        {
            Version = dto.Version,
            Units = dto.Units,
            Width = dto.Document.Width,
            Height = dto.Document.Height,
            Reference = dto.Reference
        };

        document.Layers.AddRange(dto.Layers);
        document.Entities.AddRange(dto.Entities.Select(FromDto));
        document.Constraints.AddRange(dto.Constraints ?? new List<SketchConstraint>());
        return document;
    }

    private static EntityDto ToDto(Entity entity)
    {
        return entity switch
        {
            LineEntity line => EntityDto.Line(line.Id, line.Layer, line.Start, line.End),
            CircleEntity circle => EntityDto.Circle(circle.Id, circle.Layer, circle.Center, circle.Radius),
            ArcEntity arc => EntityDto.Arc(arc.Id, arc.Layer, arc.Center, arc.Radius, arc.StartAngleDeg, arc.EndAngleDeg, arc.IsClockwise),
            _ => throw new NotSupportedException($"Unsupported entity type '{entity.GetType().Name}'.")
        };
    }

    private static Entity FromDto(EntityDto dto)
    {
        return dto.Type switch
        {
            "line" when dto.Start is not null && dto.End is not null =>
                new LineEntity(dto.Id, dto.Layer, dto.Start.Value, dto.End.Value),
            "circle" when dto.Center is not null && dto.Radius is not null =>
                new CircleEntity(dto.Id, dto.Layer, dto.Center.Value, dto.Radius.Value),
            "arc" when dto.Center is not null && dto.Radius is not null && dto.StartAngleDeg is not null && dto.EndAngleDeg is not null =>
                new ArcEntity(dto.Id, dto.Layer, dto.Center.Value, dto.Radius.Value, dto.StartAngleDeg.Value, dto.EndAngleDeg.Value, dto.IsClockwise),
            _ => throw new InvalidOperationException($"Invalid or unsupported entity record '{dto.Type}'.")
        };
    }

    private sealed record DocumentDto(
        int Version,
        string Units,
        DocumentSizeDto Document,
        ReferenceImage? Reference,
        List<Layer> Layers,
        List<EntityDto> Entities,
        List<SketchConstraint>? Constraints);

    private sealed record DocumentSizeDto(double Width, double Height);

    private sealed record EntityDto(
        string Type,
        Guid Id,
        string Layer,
        Point2? Start,
        Point2? End,
        Point2? Center,
        double? Radius,
        double? StartAngleDeg,
        double? EndAngleDeg,
        bool IsClockwise)
    {
        public static EntityDto Line(Guid id, string layer, Point2 start, Point2 end) =>
            new("line", id, layer, start, end, null, null, null, null, false);

        public static EntityDto Circle(Guid id, string layer, Point2 center, double radius) =>
            new("circle", id, layer, null, null, center, radius, null, null, false);

        public static EntityDto Arc(Guid id, string layer, Point2 center, double radius, double startAngleDeg, double endAngleDeg, bool isClockwise) =>
            new("arc", id, layer, null, null, center, radius, startAngleDeg, endAngleDeg, isClockwise);
    }
}
