import React, { forwardRef, useEffect, useRef } from "react";

const PX = 2;
const W = 500;
const H = 420;
const LEAF_FLOOR = H - 60;


function buildTree(seed: number) {
  const woodGrid = new Uint8Array(W * H);
  const leafGrid = new Uint8Array(W * H);

  let s = seed;
  function rand() {
    s = (s * 16807 + 0) % 2147483647;
    return (s & 0x7fffffff) / 0x7fffffff;
  }

  function setWood(x: number, y: number, shade: number) {
    const px = Math.round(x), py = Math.round(y);
    if (px >= 0 && px < W && py >= 0 && py < H) {
      const idx = py * W + px;
      if (woodGrid[idx] === 0) woodGrid[idx] = shade;
    }
  }

  function setLeaf(x: number, y: number, shade: number) {
    const px = Math.round(x), py = Math.round(y);
    if (px >= 0 && px < W && py >= 0 && py < LEAF_FLOOR) {
      const idx = py * W + px;
      if (shade > leafGrid[idx]) leafGrid[idx] = shade;
    }
  }

  function drawLeaf(x: number, y: number, shade: number) {
    const px = Math.round(x), py = Math.round(y);
    setLeaf(px, py, shade);
    const r = rand();
    if (r < 0.60) setLeaf(px + (rand() > 0.5 ? 1 : -1), py, shade);
    if (r < 0.32) setLeaf(px, py + (rand() > 0.45 ? -1 : 1), shade);
  }

  function leafScatter(cx: number, cy: number, radius: number, count: number) {
    for (let i = 0; i < count; i++) {
      const a = rand() * Math.PI * 2;
      const r = Math.sqrt(rand()) * radius;
      const lx = cx + Math.cos(a) * r * 0.5;
      const vertStretch = 1.0 + (cy / H) * 3.0;
      const ly = cy + Math.abs(Math.sin(a)) * r * vertStretch;
      const yFr = (ly - cy) / radius;
      const shade = yFr < 0.5 ? 3 : yFr < 1.2 ? 2 : 1;
      drawLeaf(lx, ly, shade);
    }
  }

  function drawLine(
    x0: number, y0: number, x1: number, y1: number,
    thickness: number, shade: number,
  ) {
    const dx = x1 - x0, dy = y1 - y0;
    const dist = Math.sqrt(dx * dx + dy * dy);
    const steps = Math.max(Math.ceil(dist * 2), 1);
    for (let i = 0; i <= steps; i++) {
      const t = i / steps;
      const bx = x0 + dx * t, by = y0 + dy * t;
      const r = (thickness / 2) * (1 - t * 0.4);
      for (let oy = -Math.ceil(r); oy <= Math.ceil(r); oy++)
        for (let ox = -Math.ceil(r); ox <= Math.ceil(r); ox++)
          if (ox * ox + oy * oy <= r * r) setWood(bx + ox, by + oy, shade);
    }
  }

  function branch(
    x: number, y: number,
    angle: number, length: number,
    thickness: number, depth: number,
  ) {
    const endX = x + Math.cos(angle) * length;
    const endY = y + Math.sin(angle) * length;

    const woodShade = thickness > 5 ? 1 : thickness > 2.5 ? 2 : 3;
    drawLine(x, y, endX, endY, thickness, woodShade);

    if (depth <= 0 || length < 1.5) {
      const r = 4 + rand() * 6;

      const normalised = ((angle % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2);
      const deg = normalised * (180 / Math.PI);
      const isNearHorizontal = deg < 30 || deg > 150;

      if (isNearHorizontal) {
        for (let i = 0; i < Math.round(r * r * 0.8); i++) {
          const ox = (rand() - 0.5) * r * 2;
          const oy = -rand() * r * 2.5;
          const shade = oy < -r ? 3 : 2;
          drawLeaf(endX + ox, endY + oy, shade);
        }
      } else {
        leafScatter(endX, endY, r, Math.round(r * r * 0.6));
      }
      return;
    }

    if (depth === 1) {
      const steps = Math.ceil(length / 5);
      for (let i = 0; i <= steps; i++) {
        const t = i / steps;
        const mx = x + Math.cos(angle) * length * t;
        const my = y + Math.sin(angle) * length * t;
        leafScatter(mx, my, 3 + rand() * 2, 8 + Math.round(rand() * 6));
      }
    }

    const numChildren = depth > 4 ? 2 : 3 + (rand() > 0.6 ? 1 : 0);
    for (let i = 0; i < numChildren; i++) {
      const spread = 0.30 + rand() * 0.20;
      const angleOffset = (i - (numChildren - 1) / 2) * spread;
      const droopBias = 0;
      const childAngle = angle + angleOffset + (rand() - 0.5) * 0.1 + droopBias;
      const lenFactor = 0.60 + rand() * 0.12;
      const thickFactor = 0.50 + rand() * 0.10;
      branch(endX, endY, childAngle, length * lenFactor,
        Math.max(1, thickness * thickFactor), depth - 1);
    }

    if (depth > 1 && rand() > 0.25) {
      const t = 0.25 + rand() * 0.35;
      const sx = x + Math.cos(angle) * length * t;
      const sy = y + Math.sin(angle) * length * t;
      const side = rand() > 0.5 ? 1 : -1;
      branch(sx, sy,
        angle + side * (0.30 + rand() * 0.35), length * 0.5,
        Math.max(1, thickness * 0.45), depth - 2);
    }
  }

  const cx = W / 2;
  const trunkTop = H - 150;
  const base = H - 50;


  for (let ty = trunkTop; ty <= base; ty++) {
    const t = (ty - trunkTop) / (base - trunkTop);
    const width = 10 + t * t * 16;
    const hw = Math.ceil(width);
    for (let ox = -hw; ox <= hw; ox++) {
      const px = Math.round(cx + ox), py = ty;
      if (px < 0 || px >= W || py < 0 || py >= H) continue;
      woodGrid[py * W + px] = 2;
    }
  }


  branch(cx - 30, trunkTop + 8, -Math.PI * 0.95, 70, 11, 7);
  branch(cx - 22, trunkTop + 4, -Math.PI * 0.87, 68, 10, 7);
  branch(cx - 14, trunkTop, -Math.PI * 0.78, 66, 10, 7);
  branch(cx - 8, trunkTop - 4, -Math.PI * 0.68, 64, 9, 7);
  branch(cx - 8, trunkTop + 4, -Math.PI * 0.70, 71, 10, 7);
  branch(cx - 4, trunkTop - 7, -Math.PI * 0.60, 62, 9, 7);
  branch(cx, trunkTop - 6, -Math.PI * 0.55, 70, 9, 7);
  branch(cx, trunkTop - 7, -Math.PI * 0.50, 73, 9, 7);
  branch(cx, trunkTop - 7, -Math.PI * 0.55, 77, 9, 7);
  branch(cx, trunkTop - 6, -Math.PI * 0.45, 70, 9, 7);
  branch(cx, trunkTop - 6, -Math.PI * 0.60, 70, 9, 7);
  branch(cx, trunkTop - 6, -Math.PI * 0.40, 70, 9, 7);
  branch(cx + 30, trunkTop + 8, -Math.PI * 0.1, 65, 11, 7);
  branch(cx + 22, trunkTop + 4, -Math.PI * 0.18, 65, 10, 7);
  branch(cx + 14, trunkTop, -Math.PI * 0.22, 63, 10, 7);
  branch(cx + 14, trunkTop, -Math.PI * 0.25, 65, 10, 7);
  branch(cx + 8, trunkTop - 4, -Math.PI * 0.32, 61, 9, 7);
  branch(cx + 4, trunkTop - 7, -Math.PI * 0.40, 62, 9, 7);
  branch(cx + 2, trunkTop + 4, -Math.PI * 0.45, 67, 10, 7);
  branch(cx + 2, trunkTop + 5, -Math.PI * 0.17, 67, 10, 7);
  branch(cx - 10, trunkTop + 20, -Math.PI * 0.75, 45, 7, 6);
  branch(cx - 5, trunkTop + 20, -Math.PI * 0.62, 42, 6, 6);
  branch(cx, trunkTop + 18, -Math.PI * 0.50, 44, 7, 6);
  branch(cx + 10, trunkTop + 20, -Math.PI * 0.25, 45, 7, 6);
  branch(cx + 5, trunkTop + 20, -Math.PI * 0.38, 42, 6, 6);
  branch(cx + 3, trunkTop - 6, -Math.PI * 0.68, 10, 9, 7);
  branch(cx - 3, trunkTop - 7, -Math.PI * 0.32, 10, 9, 7);
  branch(cx + 3, trunkTop - 6, -Math.PI * 0.7, 5, 9, 7);
  branch(cx - 3, trunkTop - 7, -Math.PI * 0.3, 5, 9, 7);
  branch(cx + 3, trunkTop - 6, -Math.PI * 0.73, 20, 9, 7);
  branch(cx, trunkTop - 3, -Math.PI * 0.15, 90, 5, 3);



  return { woodGrid, leafGrid };
}
const WOOD_COLORS = ['', '#c96303', '#c96303', '#c96303', '#321901'];
const LEAF_COLORS = ['', '#ed5145', '#fdb068', '#fee5cd'];
const GROUND_COLORS = ['#fee5cd', '#fdca9b', '#fdb068'];
const GRASS_COLORS = ['#ed5145', '#fb7b04', '#c96303', '#f17c74'];

function hexToRgba(hex: string) {
  return [
    parseInt(hex.slice(1, 3), 16),
    parseInt(hex.slice(3, 5), 16),
    parseInt(hex.slice(5, 7), 16),
    255,
  ];
}

const COLOR_RGBA = new Map(
  [...WOOD_COLORS, ...LEAF_COLORS, ...GROUND_COLORS, ...GRASS_COLORS]
    .filter(Boolean)
    .map((color) => [color, hexToRgba(color)]),
);

export const TRUNK_TOP_CSS = (H - 150) + 10;

let cachedTreeCanvas: HTMLCanvasElement | null = null;

function renderTreeCanvas() {
  if (cachedTreeCanvas) return cachedTreeCanvas;

  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d")!;
  canvas.width = W * PX;
  canvas.height = H * PX;
  ctx.imageSmoothingEnabled = false;

  const { woodGrid, leafGrid } = buildTree(55);
  const pixelCanvas = document.createElement("canvas");
  const pixelCtx = pixelCanvas.getContext("2d")!;
  pixelCanvas.width = W;
  pixelCanvas.height = H;
  const image = pixelCtx.createImageData(W, H);

  function setPixel(x: number, y: number, color: string) {
    const rgba = COLOR_RGBA.get(color)!;
    const index = (y * W + x) * 4;
    image.data[index] = rgba[0];
    image.data[index + 1] = rgba[1];
    image.data[index + 2] = rgba[2];
    image.data[index + 3] = rgba[3];
  }

  for (let y = 0; y < H; y++)
    for (let x = 0; x < W; x++) {
      const v = woodGrid[y * W + x];
      if (v) setPixel(x, y, WOOD_COLORS[v] ?? '#321901');
    }

  for (let y = 0; y < H; y++)
    for (let x = 0; x < W; x++) {
      const v = leafGrid[y * W + x];
      if (v) setPixel(x, y, LEAF_COLORS[v]);
    }

  const GROUND_Y = H - 50;

  let gs = 12345;
  function grassRand() {
    gs = (gs * 16807 + 0) % 2147483647;
    return (gs & 0x7fffffff) / 0x7fffffff;
  }

  for (let x = 0; x < W; x++) {
    const wave = Math.sin(x * 0.05) * 3 + Math.sin(x * 0.12) * 2;
    const groundTop = Math.round(GROUND_Y + wave);
    for (let y = groundTop; y < H; y++) {
      const depth = (y - groundTop) / (H - groundTop);
      const ci = depth < 0.3 ? 0 : depth < 0.7 ? 1 : 2;
      setPixel(x, y, GROUND_COLORS[ci]);
    }
  }

  for (let x = 0; x < W; x++) {
    if (grassRand() > 0.35) continue;
    const wave = Math.sin(x * 0.05) * 3 + Math.sin(x * 0.12) * 2;
    const surfaceY = Math.round(GROUND_Y + wave);
    const bladeH = Math.floor(grassRand() * 4) + 2;
    const color = GRASS_COLORS[Math.floor(grassRand() * GRASS_COLORS.length)];
    for (let b = 0; b < bladeH; b++) {
      setPixel(x, surfaceY - b - 1, color);
    }
    if (grassRand() > 0.6 && x + 1 < W) {
      setPixel(x + 1, surfaceY - 1, color);
    }
  }

  pixelCtx.putImageData(image, 0, 0);
  ctx.drawImage(pixelCanvas, 0, 0, canvas.width, canvas.height);
  cachedTreeCanvas = canvas;
  return canvas;
}

export function prewarmTreeCanvas() {
  renderTreeCanvas();
}

const Tree = forwardRef<HTMLCanvasElement>(function Tree(_, ref) {
  const internalRef = useRef<HTMLCanvasElement>(null);
  const canvasRef = (ref as React.RefObject<HTMLCanvasElement>) ?? internalRef;

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return undefined;

    const draw = () => {
      const ctx = canvas.getContext("2d");
      if (!ctx) return;
      canvas.width = W * PX;
      canvas.height = H * PX;
      ctx.drawImage(renderTreeCanvas(), 0, 0);
    };
    const requestIdle = window.requestIdleCallback ?? ((callback) => window.setTimeout(callback, 100));
    const cancelIdle = window.cancelIdleCallback ?? window.clearTimeout;
    const idleId = requestIdle(draw, { timeout: 700 });

    return () => cancelIdle(idleId);
  }, []);

  return (
    <canvas
      ref={canvasRef}
      className="tree"
      style={{ imageRendering: "pixelated" }}
    />
  );
});

export default Tree;
