import { z } from 'zod';

/**
 * Universal VTT (.dd2vtt / .uvtt / .df2vtt) interchange — exported by Dungeondraft
 * and importable by Foundry/Roll20/etc. JSON with a base64 image, grid size, and
 * line-of-sight walls / portals (doors) / lights expressed in GRID units.
 *
 * We convert everything to image-pixel space (× pixels_per_grid) so it lines up
 * with our tokens/drawings, and `pixels_per_grid` becomes our `gridSize`.
 */

const pt = z.object({ x: z.number(), y: z.number() });

const uvttSchema = z.object({
  format: z.number().optional(),
  resolution: z.object({
    map_origin: pt.optional(),
    map_size: pt.optional(),
    pixels_per_grid: z.number().optional(),
  }).optional(),
  line_of_sight: z.array(z.array(pt)).optional(),
  objects_line_of_sight: z.array(z.array(pt)).optional(),
  portals: z.array(z.object({
    position: pt.optional(),
    bounds: z.array(pt).optional(),
    closed: z.boolean().optional(),
  })).optional(),
  lights: z.array(z.object({
    position: pt.optional(),
    range: z.number().optional(),
    color: z.string().optional(),
    intensity: z.number().optional(),
  })).optional(),
  image: z.string().optional(),
}).passthrough();

export interface ParsedUvtt {
  image: string; // data URL
  gridSize: number; // px per cell
  cols: number;
  rows: number;
  walls: { points: { x: number; y: number }[] }[];
  doors: { a: { x: number; y: number }; b: { x: number; y: number }; open: boolean }[];
  lights: { x: number; y: number; range: number; color: string; intensity: number }[];
}

/** Coerce a UVTT colour ("RRGGBBAA"/"AARRGGBB"/"#rgb") to a #rrggbb string. */
function toHexColor(c: string | undefined): string {
  if (!c) return '#ffd9a0';
  const hex = c.replace(/^#/, '');
  if (hex.length === 6) return `#${hex}`;
  if (hex.length === 8) return `#${hex.slice(-6)}`; // drop leading alpha
  if (hex.length === 3) return `#${hex}`;
  return '#ffd9a0';
}

/** Parse UVTT JSON text. Returns null if it isn't a usable VTT map (no image). */
export function parseUvtt(text: string): ParsedUvtt | null {
  let json: unknown;
  try { json = JSON.parse(text); } catch { return null; }
  const parsed = uvttSchema.safeParse(json);
  if (!parsed.success) return null;
  const u = parsed.data;
  if (!u.image) return null; // line-of-sight-only files aren't maps we can show

  const ppg = u.resolution?.pixels_per_grid && u.resolution.pixels_per_grid > 0 ? u.resolution.pixels_per_grid : 70;
  const origin = u.resolution?.map_origin ?? { x: 0, y: 0 };
  const toPx = (p: { x: number; y: number }) => ({ x: (p.x - origin.x) * ppg, y: (p.y - origin.y) * ppg });

  const size = u.resolution?.map_size ?? { x: 0, y: 0 };
  const cols = Math.max(0, Math.round(size.x));
  const rows = Math.max(0, Math.round(size.y));

  const segs = [...(u.line_of_sight ?? []), ...(u.objects_line_of_sight ?? [])];
  const walls = segs.map((poly) => ({ points: poly.map(toPx) })).filter((w) => w.points.length >= 2);

  const doors = (u.portals ?? []).flatMap((p) => {
    const b = p.bounds;
    if (!b || b.length < 2 || !b[0] || !b[1]) return [];
    return [{ a: toPx(b[0]), b: toPx(b[1]), open: p.closed === false }];
  });

  const lights = (u.lights ?? []).flatMap((l) => {
    if (!l.position) return [];
    const c = toPx(l.position);
    return [{ x: c.x, y: c.y, range: (l.range ?? 0) * ppg, color: toHexColor(l.color), intensity: l.intensity ?? 0.5 }];
  });

  // PNG is the UVTT default; browsers decode by content regardless of declared type.
  const image = u.image.startsWith('data:') ? u.image : `data:image/png;base64,${u.image}`;
  return { image, gridSize: Math.round(ppg), cols, rows, walls, doors, lights };
}

/** Build a UVTT object from one of our maps (image data URL + walls), for export. */
export function toUvtt(map: {
  image: string;
  gridSize: number;
  cols: number;
  rows: number;
  walls: { points: { x: number; y: number }[] }[];
  doors: { a: { x: number; y: number }; b: { x: number; y: number }; open: boolean }[];
  lights: { x: number; y: number; range: number; color: string; intensity: number }[];
}): Record<string, unknown> {
  const ppg = map.gridSize || 70;
  const toGrid = (p: { x: number; y: number }) => ({ x: p.x / ppg, y: p.y / ppg });
  const base64 = map.image.replace(/^data:[^,]*,/, '');
  return {
    format: 0.3,
    resolution: {
      map_origin: { x: 0, y: 0 },
      map_size: { x: map.cols, y: map.rows },
      pixels_per_grid: ppg,
    },
    line_of_sight: map.walls.map((w) => w.points.map(toGrid)),
    portals: map.doors.map((d) => ({
      position: toGrid({ x: (d.a.x + d.b.x) / 2, y: (d.a.y + d.b.y) / 2 }),
      bounds: [toGrid(d.a), toGrid(d.b)],
      rotation: 0,
      closed: !d.open,
      freestanding: false,
    })),
    lights: map.lights.map((l) => ({
      position: toGrid({ x: l.x, y: l.y }),
      range: l.range / ppg,
      intensity: l.intensity,
      color: l.color.replace('#', ''),
      shadows: true,
    })),
    image: base64,
  };
}
