import { reactive } from 'vue';
import type { Box, BoxModel } from '../utils/rect';
import type { Rgba } from '../utils/color';
import type { Capture } from '../content/capture';

export interface ColorSwatch {
  label: string;
  color: Rgba;
  hex: string;
  varName: string | null;
  /** The value is inherited from an ancestor rather than set on the element itself. */
  inherited?: boolean;
}

/** A generic computed-style line shown in the panel. */
export interface StyleItem {
  label: string;
  value: string;
  inherited?: boolean;
}

export interface LayoutProp {
  label: string;
  value: string;
}

export interface LayoutInfo {
  /** Computed `display` (e.g. `flex`, `grid`, `block`, `inline-flex`). */
  display: string;
  /** Coarse classification used to tint the panel and select an overlay. */
  kind: 'flex' | 'grid' | 'block';
  /** Container properties (flow, alignment, gap) plus the element's own item placement. */
  props: LayoutProp[];
  /** Grid line positions in iframe-content pixels (for the visual overlay), or null. */
  gridLines: { xs: number[]; ys: number[] } | null;
  /** Gap rectangles (between grid tracks / flex items) in iframe-content pixels. */
  gaps: Box[];
  /** Child item border boxes in iframe-content pixels. */
  items: Box[];
}

export interface Inspection {
  tag: string;
  id: string;
  classes: string[];
  /** Box-model rects in iframe-content pixels. */
  box: BoxModel;
  width: number;
  height: number;
  radius: string;
  padding: string;
  margin: string;
  layout: LayoutInfo;
  typography: StyleItem[];
  effects: StyleItem[];
  colors: ColorSwatch[];
}

/** What the layout overlay should emphasize, driven by hovering panel rows. */
export type LayoutHighlight = 'none' | 'gap' | 'tracks' | 'items';

export interface DevicePreset {
  label: string;
  width: number;
  height: number;
}

export const DEVICE_PRESETS: DevicePreset[] = [
  { label: '320', width: 320, height: 568 },
  { label: '375', width: 375, height: 667 },
  { label: '768', width: 768, height: 1024 },
  { label: '1024', width: 1024, height: 768 },
  { label: '1440', width: 1440, height: 900 },
];

export const MIN_ZOOM = 0.1;
export const MAX_ZOOM = 4;
const MIN_FRAME = 80;

interface State {
  srcdoc: string;
  tag: string;
  naturalWidth: number;
  naturalHeight: number;
  frameWidth: number;
  frameHeight: number;
  zoom: number;
  panX: number;
  panY: number;
  showRulers: boolean;
  /** Visualize the selected element's grid/flex structure on the canvas. */
  showGrid: boolean;
  /** When false (default), clicks inside the frame never re-select — avoids misclicks. */
  clicksEnabled: boolean;
  /** Panel-driven emphasis for the layout overlay. */
  layoutHighlight: LayoutHighlight;
  guides: { x: number[]; y: number[] };
  hover: Inspection | null;
  selected: Inspection | null;
  viewportW: number;
  viewportH: number;
}

export const state = reactive<State>({
  srcdoc: '',
  tag: '',
  naturalWidth: 0,
  naturalHeight: 0,
  frameWidth: 0,
  frameHeight: 0,
  zoom: 1,
  panX: 0,
  panY: 0,
  showRulers: true,
  showGrid: true,
  clicksEnabled: false,
  layoutHighlight: 'none',
  guides: { x: [], y: [] },
  hover: null,
  selected: null,
  viewportW: 0,
  viewportH: 0,
});

const clamp = (n: number, min: number, max: number): number => Math.max(min, Math.min(max, n));

let exitHandler: (() => void) | null = null;
export function onExit(fn: () => void): void {
  exitHandler = fn;
}
export function requestExit(): void {
  exitHandler?.();
}

export function initFromCapture(capture: Capture): void {
  state.srcdoc = capture.srcdoc;
  state.tag = capture.tag;
  state.naturalWidth = capture.naturalWidth;
  state.naturalHeight = capture.naturalHeight;
  // Give the frame breathing room around the natural-sized block.
  state.frameWidth = Math.max(MIN_FRAME, capture.naturalWidth + 64);
  state.frameHeight = Math.max(MIN_FRAME, capture.naturalHeight + 64);
  state.zoom = 1;
  state.clicksEnabled = false;
  state.layoutHighlight = 'none';
  state.guides = { x: [], y: [] };
  state.hover = null;
  state.selected = null;
}

export function setFrameSize(width: number, height: number): void {
  state.frameWidth = Math.max(MIN_FRAME, Math.round(width));
  state.frameHeight = Math.max(MIN_FRAME, Math.round(height));
}

export function setDevice(preset: DevicePreset): void {
  setFrameSize(preset.width, preset.height);
}

export function rotateFrame(): void {
  setFrameSize(state.frameHeight, state.frameWidth);
}

export function resetSize(): void {
  setFrameSize(state.naturalWidth + 64, state.naturalHeight + 64);
  state.zoom = 1;
}

export function setZoom(zoom: number): void {
  state.zoom = clamp(zoom, MIN_ZOOM, MAX_ZOOM);
}

/** Zoom by a factor while keeping the viewport point (cx, cy) anchored. */
export function zoomAt(factor: number, cx: number, cy: number): void {
  const next = clamp(state.zoom * factor, MIN_ZOOM, MAX_ZOOM);
  const ratio = next / state.zoom;
  state.panX = cx - (cx - state.panX) * ratio;
  state.panY = cy - (cy - state.panY) * ratio;
  state.zoom = next;
}

/** Center the frame within a viewport of the given size. */
export function centerIn(viewportWidth: number, viewportHeight: number): void {
  state.panX = Math.round((viewportWidth - state.frameWidth * state.zoom) / 2);
  state.panY = Math.round((viewportHeight - state.frameHeight * state.zoom) / 2);
}

/** Re-center using the last known viewport size (tracked by the Stage). */
export function recenter(): void {
  if (state.viewportW && state.viewportH) centerIn(state.viewportW, state.viewportH);
}

/** Append a guide on the given axis and return its index (so a drag can keep updating it). */
export function addGuide(axis: 'x' | 'y', position: number): number {
  return state.guides[axis].push(Math.round(position)) - 1;
}

export function updateGuide(axis: 'x' | 'y', index: number, position: number): void {
  const arr = state.guides[axis];
  if (index >= 0 && index < arr.length) arr[index] = Math.round(position);
}

export function removeGuide(axis: 'x' | 'y', index: number): void {
  const arr = state.guides[axis];
  if (index >= 0 && index < arr.length) arr.splice(index, 1);
}

export function clearGuides(): void {
  state.guides = { x: [], y: [] };
}
