import { novoId, solida, type PecaDeTraco, type Pintura, type PontoDoTraco } from "./documento";

/*
 * Silhuetas de verdade para os elementos prontos (cupom com mordida, selo
 * serrilhado, etiqueta com furo, roseta, escudo): o contorno é desenhado com
 * retas e arcos exatos (Bézier), sem bolinha da cor do fundo por cima. Cada
 * uma vira um desenho fechado (traço), que continua editável nó a nó e entra
 * no Soldar/Aparar. Medidas em px de uma caixa que começa em 0,0.
 */

type Par = { x: number; y: number };

/** A caneta: retas, arcos de círculo e curvas, em px. */
export class Caneta {
  nos: PontoDoTraco[] = [];

  private ultimo() {
    return this.nos[this.nos.length - 1];
  }

  /** Reta até o ponto (o primeiro ponto só começa o desenho). */
  ate(x: number, y: number) {
    const u = this.ultimo();
    if (!u || Math.hypot(u.x - x, u.y - y) > 1e-6) this.nos.push({ x, y });
    return this;
  }

  /** Arco do círculo (centro, raio) de um ângulo a outro, em graus (0 = direita, 90 = baixo); o sentido é o do sinal. */
  arco(cx: number, cy: number, r: number, de: number, ate: number) {
    if (r <= 0.01) return this.ate(cx, cy);
    const passos = Math.max(1, Math.ceil(Math.abs(ate - de) / 90));
    const em = (a: number): Par => ({ x: cx + r * Math.cos((a * Math.PI) / 180), y: cy + r * Math.sin((a * Math.PI) / 180) });
    const tangente = (a: number): Par => ({ x: -Math.sin((a * Math.PI) / 180), y: Math.cos((a * Math.PI) / 180) });
    this.ate(em(de).x, em(de).y);
    for (let i = 0; i < passos; i += 1) {
      const a0 = de + ((ate - de) * i) / passos;
      const a1 = de + ((ate - de) * (i + 1)) / passos;
      const k = (4 / 3) * Math.tan((((a1 - a0) * Math.PI) / 180) / 4) * r;
      const p0 = em(a0);
      const p1 = em(a1);
      const t0 = tangente(a0);
      const t1 = tangente(a1);
      const u = this.ultimo();
      u.s = { x: p0.x + t0.x * k, y: p0.y + t0.y * k };
      this.nos.push({ x: p1.x, y: p1.y, e: { x: p1.x - t1.x * k, y: p1.y - t1.y * k } });
    }
    return this;
  }

  /** Curva até o ponto, com as duas alças. */
  curva(c1: Par, c2: Par, x: number, y: number) {
    const u = this.ultimo();
    if (u) u.s = c1;
    this.nos.push({ x, y, e: c2 });
    return this;
  }

  /** Os nós do contorno fechado (o último em cima do primeiro vira um só). */
  fechar(): PontoDoTraco[] {
    const nos = this.nos.map((q) => ({ ...q }));
    const a = nos[0];
    const b = nos[nos.length - 1];
    if (nos.length > 2 && Math.hypot(a.x - b.x, a.y - b.y) < 1e-6) {
      a.e = b.e ?? a.e;
      nos.pop();
    }
    return nos;
  }
}

/* As mordidas de cada lado: onde (fração do lado) e se entram ("dentro", a mordida do cupom) ou saem ("fora", o recorte ondulado). */
export type Mordidas = { onde: number[]; raio: number; fora?: boolean };

/**
 * Retângulo de cantos redondos com mordidas de meio círculo nos lados (o
 * cupom, o ingresso com canhoto, o selo serrilhado). Sentido horário a partir
 * do canto de cima à esquerda.
 */
export function cartaoRecortado(w: number, h: number, opcoes: { raio?: number; topo?: Mordidas; direita?: Mordidas; base?: Mordidas; esquerda?: Mordidas }): PontoDoTraco[] {
  const r = Math.min(opcoes.raio ?? 0, w / 2, h / 2);
  const c = new Caneta();
  const lado = (m: Mordidas | undefined, comprimento: number) => (m ? [...m.onde].sort((a, b) => a - b).map((f) => f * comprimento) : []);
  c.ate(r, 0);
  for (const x of lado(opcoes.topo, w)) {
    const m = opcoes.topo!;
    c.ate(x - m.raio, 0).arco(x, 0, m.raio, 180, m.fora ? 360 : 0);
  }
  c.arco(w - r, r, r, -90, 0);
  for (const y of lado(opcoes.direita, h)) {
    const m = opcoes.direita!;
    c.ate(w, y - m.raio).arco(w, y, m.raio, -90, m.fora ? 90 : -270);
  }
  c.arco(w - r, h - r, r, 0, 90);
  for (const x of lado(opcoes.base, w).reverse()) {
    const m = opcoes.base!;
    c.ate(x + m.raio, h).arco(x, h, m.raio, 0, m.fora ? 180 : -180);
  }
  c.arco(r, h - r, r, 90, 180);
  for (const y of lado(opcoes.esquerda, h).reverse()) {
    const m = opcoes.esquerda!;
    c.ate(0, y + m.raio).arco(0, y, m.raio, 90, m.fora ? 270 : -90);
  }
  c.arco(r, r, r, 180, 270);
  return c.fechar();
}

/** As mordidas espalhadas por igual ao longo do lado (o serrilhado), sem encostar nos cantos. */
export function serrilha(comprimento: number, raio: number, passo: number, margem = passo / 2): number[] {
  const quantas = Math.max(1, Math.floor((comprimento - margem * 2) / passo) + 1);
  const usado = (quantas - 1) * passo;
  const inicio = (comprimento - usado) / 2;
  return Array.from({ length: quantas }, (_, i) => (inicio + i * passo) / comprimento).filter((f) => f * comprimento - raio > 0 && f * comprimento + raio < comprimento);
}

/** Polígono com os cantos arredondados (raio em px; cada canto vira uma curva curta). */
export function poligonoRedondo(pontos: Par[], raio: number | number[]): PontoDoTraco[] {
  const n = pontos.length;
  const nos: PontoDoTraco[] = [];
  pontos.forEach((v, i) => {
    const a = pontos[(i - 1 + n) % n];
    const b = pontos[(i + 1) % n];
    const r = Array.isArray(raio) ? raio[i] : raio;
    const da = Math.hypot(a.x - v.x, a.y - v.y);
    const db = Math.hypot(b.x - v.x, b.y - v.y);
    if (r <= 0 || !da || !db) {
      nos.push({ x: v.x, y: v.y });
      return;
    }
    const ta = Math.min(r / da, 0.5);
    const tb = Math.min(r / db, 0.5);
    const p1 = { x: v.x + (a.x - v.x) * ta, y: v.y + (a.y - v.y) * ta };
    const p2 = { x: v.x + (b.x - v.x) * tb, y: v.y + (b.y - v.y) * tb };
    nos.push({ ...p1, s: { x: p1.x + (v.x - p1.x) * 0.55, y: p1.y + (v.y - p1.y) * 0.55 } });
    nos.push({ ...p2, e: { x: p2.x + (v.x - p2.x) * 0.55, y: p2.y + (v.y - p2.y) * 0.55 } });
  });
  return nos;
}

/** Círculo (para furo ou ilha), em sentido contrário ao do contorno de fora. */
export function circuloDeNos(cx: number, cy: number, r: number, horario = false): PontoDoTraco[] {
  return new Caneta().arco(cx, cy, r, 0, horario ? 360 : -360).fechar();
}

/** Estrela de muitas pontas com as pontas redondas (a roseta dos selos). */
export function roseta(cx: number, cy: number, raio: number, pontas: number, fundo: number, arredondar: number): PontoDoTraco[] {
  const pts: Par[] = [];
  for (let i = 0; i < pontas * 2; i += 1) {
    const a = (Math.PI * i) / pontas - Math.PI / 2;
    const r = i % 2 === 0 ? raio : raio * fundo;
    pts.push({ x: cx + r * Math.cos(a), y: cy + r * Math.sin(a) });
  }
  return poligonoRedondo(pts, arredondar);
}

/** O escudo (selo de garantia): topo reto com ombros, ponta embaixo em curva. */
export function escudo(w: number, h: number): PontoDoTraco[] {
  const c = new Caneta();
  c.ate(w * 0.5, 0)
    .curva({ x: w * 0.66, y: h * 0.07 }, { x: w * 0.84, y: h * 0.1 }, w, h * 0.1)
    .ate(w, h * 0.42)
    .curva({ x: w, y: h * 0.72 }, { x: w * 0.78, y: h * 0.9 }, w * 0.5, h)
    .curva({ x: w * 0.22, y: h * 0.9 }, { x: 0, y: h * 0.72 }, 0, h * 0.42)
    .ate(0, h * 0.1)
    .curva({ x: w * 0.16, y: h * 0.1 }, { x: w * 0.34, y: h * 0.07 }, w * 0.5, 0);
  return c.fechar();
}

/**
 * A peça com os contornos (em px, a partir da posição dada): a caixa justa em
 * volta de todos e os nós em fração dela, como os desenhos da caneta.
 */
export function silhueta(contornos: PontoDoTraco[][], x: number, y: number, pintura: Pintura, campos: Partial<PecaDeTraco> = {}): PecaDeTraco {
  let x0 = Infinity;
  let y0 = Infinity;
  let x1 = -Infinity;
  let y1 = -Infinity;
  for (const c of contornos)
    for (const q of c)
      for (const v of [q, q.e, q.s]) {
        if (!v) continue;
        x0 = Math.min(x0, v.x);
        y0 = Math.min(y0, v.y);
        x1 = Math.max(x1, v.x);
        y1 = Math.max(y1, v.y);
      }
  const w = Math.max(1, x1 - x0);
  const h = Math.max(1, y1 - y0);
  const fracao = (v: Par) => ({ x: Math.round(((v.x - x0) / w) * 1e5) / 1e5, y: Math.round(((v.y - y0) / h) * 1e5) / 1e5 });
  const [pontos, ...resto] = contornos.map((c) => c.map((q) => ({ ...fracao(q), ...(q.e ? { e: fracao(q.e) } : {}), ...(q.s ? { s: fracao(q.s) } : {}) })));
  return {
    id: novoId("tr"),
    tipo: "traco",
    nome: "Silhueta",
    x: x + x0,
    y: y + y0,
    w,
    h,
    rot: 0,
    opacidade: 1,
    pontos,
    ...(resto.length ? { contornos: resto } : {}),
    fechado: true,
    suave: false,
    cor: "#FFFFFF",
    espessura: 0,
    tracejado: "solido",
    inicio: "nada",
    fim: "nada",
    preenchimento: pintura,
    sombra: null,
    ...campos,
  };
}

/** Linha reta (picote, divisória), de um ponto a outro, em px. */
export function linhaReta(x1: number, y1: number, x2: number, y2: number, cor: string, espessura: number, tracejado: PecaDeTraco["tracejado"] = "solido", campos: Partial<PecaDeTraco> = {}): PecaDeTraco {
  const folga = Math.max(6, espessura * 2);
  const x = Math.min(x1, x2) - folga;
  const y = Math.min(y1, y2) - folga;
  const w = Math.abs(x2 - x1) + folga * 2;
  const h = Math.abs(y2 - y1) + folga * 2;
  return {
    id: novoId("tr"),
    tipo: "traco",
    nome: tracejado === "solido" ? "Linha" : "Picote",
    x,
    y,
    w,
    h,
    rot: 0,
    opacidade: 1,
    pontos: [
      { x: (x1 - x) / w, y: (y1 - y) / h },
      { x: (x2 - x) / w, y: (y2 - y) / h },
    ],
    fechado: false,
    suave: false,
    cor,
    espessura,
    tracejado,
    inicio: "nada",
    fim: "nada",
    preenchimento: solida(cor),
    sombra: null,
    ...campos,
  };
}
