import { useEffect, useRef, useState } from 'react'
import {
  anonymizeImage,
  deidentifyImage,
  detect,
  redact,
  type DeidStrategy,
  type EntityTaxonomy,
  type PIIEntity,
  type PseudonymMapping,
  type RedactMode,
} from '../lib/api'
import { l1Rgb } from '../lib/colors'
import Legend from './Legend'
import MappingTable from './MappingTable'

type Action = 'redact' | 'deidentify' | 'anonymize'

const ACTION_BUTTON_LABELS: Record<Action, string> = {
  redact: 'Detect & Redact',
  deidentify: 'Detect & De-identify',
  anonymize: 'Detect & Anonymize',
}

const ACCEPTED_TYPES = ['image/png', 'image/jpeg', 'image/bmp', 'image/tiff', 'image/webp']

// Bundled sample documents (no real PII) under web/public/samples/ — add files
// with these exact names to enable the "try a sample" shortcuts.
const SAMPLE_IMAGES = [
  { name: 'Sample 1', src: '/samples/sample-1.jpg' },
  { name: 'Sample 2', src: '/samples/sample-2.jpg' },
  { name: 'Sample 3', src: '/samples/sample-3.jpg' },
]

async function urlToFile(url: string, name: string): Promise<File> {
  const res = await fetch(url)
  const blob = await res.blob()
  return new File([blob], name, { type: blob.type })
}

export default function ImageTab({
  entities: taxonomy,
  ready,
}: {
  entities: EntityTaxonomy | null
  ready: boolean
}) {
  const [file, setFile] = useState<File | null>(null)
  const [previewUrl, setPreviewUrl] = useState<string | null>(null)
  const [action, setAction] = useState<Action>('redact')
  const [mode, setMode] = useState<RedactMode>('blur')
  const [color, setColor] = useState('#000000')
  const [strategy, setStrategy] = useState<DeidStrategy>('counter')
  const [selected, setSelected] = useState<Set<string>>(new Set())

  // All categories selected by default, once the taxonomy arrives.
  useEffect(() => {
    if (taxonomy) setSelected(new Set([...taxonomy.text, ...taxonomy.visual]))
  }, [taxonomy])

  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [detectedEntities, setDetectedEntities] = useState<PIIEntity[]>([])
  const [redactedUrl, setRedactedUrl] = useState<string | null>(null)
  const [resultTitle, setResultTitle] = useState('Redacted')
  const [mapping, setMapping] = useState<PseudonymMapping | null>(null)

  const canvasRef = useRef<HTMLCanvasElement>(null)

  useEffect(() => {
    if (!previewUrl || !canvasRef.current) return
    const img = new Image()
    img.onload = () => {
      const canvas = canvasRef.current!
      canvas.width = img.naturalWidth
      canvas.height = img.naturalHeight
      const ctx = canvas.getContext('2d')!
      ctx.drawImage(img, 0, 0)
      for (const e of detectedEntities) {
        const [x0, y0, x1, y1] = e.bbox
        ctx.strokeStyle = l1Rgb(e.l1)
        ctx.lineWidth = Math.max(2, img.naturalWidth / 400)
        ctx.strokeRect(x0, y0, x1 - x0, y1 - y0)
        ctx.fillStyle = l1Rgb(e.l1)
        const fontSize = Math.max(12, img.naturalWidth / 90)
        ctx.font = `${fontSize}px sans-serif`
        const label = e.category
        const tw = ctx.measureText(label).width
        ctx.fillRect(x0, Math.max(0, y0 - fontSize - 4), tw + 6, fontSize + 4)
        ctx.fillStyle = '#fff'
        ctx.fillText(label, x0 + 3, Math.max(fontSize, y0 - 4))
      }
    }
    img.src = previewUrl
  }, [previewUrl, detectedEntities])

  function pickFile(f: File) {
    setFile(f)
    setPreviewUrl(URL.createObjectURL(f))
    setDetectedEntities([])
    setRedactedUrl(null)
    setMapping(null)
    setError(null)
  }

  async function pickSample(src: string, name: string) {
    try {
      pickFile(await urlToFile(src, name))
    } catch {
      setError(`Couldn't load ${name} — is it present under web/public/samples/?`)
    }
  }

  function onFileInput(e: React.ChangeEvent<HTMLInputElement>) {
    const f = e.target.files?.[0]
    if (!f) return
    if (!ACCEPTED_TYPES.includes(f.type)) {
      setError(`Unsupported file type: ${f.type || 'unknown'}`)
      return
    }
    pickFile(f)
  }

  function toggle(value: string) {
    const next = new Set(selected)
    if (next.has(value)) next.delete(value)
    else next.add(value)
    setSelected(next)
  }

  const totalCategories = taxonomy ? taxonomy.text.length + taxonomy.visual.length : 0
  const allSelected = taxonomy != null && selected.size === totalCategories
  // Omit the param entirely when everything is selected (= server default).
  const categories = allSelected ? undefined : [...selected]

  async function run() {
    if (!file || selected.size === 0) return
    setLoading(true)
    setError(null)
    setMapping(null)
    try {
      const detecting = detect(file, categories)
      if (action === 'redact') {
        const rgb = [1, 3, 5].map((i) => parseInt(color.slice(i, i + 2), 16)) as [
          number,
          number,
          number,
        ]
        const [ents, blob] = await Promise.all([detecting, redact(file, mode, rgb, categories)])
        setDetectedEntities(ents)
        setRedactedUrl(URL.createObjectURL(blob))
        setResultTitle(`Redacted · ${mode}`)
      } else if (action === 'deidentify') {
        const [ents, result] = await Promise.all([detecting, deidentifyImage(file, categories, strategy)])
        setDetectedEntities(ents)
        setRedactedUrl(result.imageUrl)
        setMapping(result.mapping)
        setResultTitle('De-identified')
      } else {
        const [ents, blob] = await Promise.all([detecting, anonymizeImage(file, categories)])
        setDetectedEntities(ents)
        setRedactedUrl(URL.createObjectURL(blob))
        setResultTitle('Anonymized')
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : String(err))
    } finally {
      setLoading(false)
    }
  }

  const groups = [...new Set(detectedEntities.map((e) => e.l1))].sort()

  return (
    <div className="tab-panel">
      <p className="tab-caption">Upload a document image — it will be detected and redacted.</p>

      <div className="panel-grid">
        <section className="card">
          <h2 className="card-title">Source</h2>
          <div className="samples-row">
            {SAMPLE_IMAGES.map((s) => (
              <button
                key={s.src}
                type="button"
                className="sample-thumb"
                onClick={() => pickSample(s.src, s.name)}
                disabled={!ready}
              >
                <img
                  src={s.src}
                  alt={s.name}
                  onError={(e) => (e.currentTarget.parentElement!.style.display = 'none')}
                />
                <span>{s.name}</span>
              </button>
            ))}
            <label className="upload-btn">
              Upload image
              <input type="file" accept={ACCEPTED_TYPES.join(',')} onChange={onFileInput} hidden />
            </label>
          </div>
        </section>

        <section className="card">
          <h2 className="card-title">Options</h2>
          <div className="controls-row">
            <label className="field">
              <span className="field-label">Action</span>
              <select value={action} onChange={(e) => setAction(e.target.value as Action)}>
                <option value="redact">Redact</option>
                <option value="anonymize">Anonymize</option>
                <option value="deidentify">De-identify</option>
              </select>
            </label>
            {action === 'deidentify' && (
              <label className="field">
                <span className="field-label">Token style</span>
                <select
                  value={strategy}
                  onChange={(e) => setStrategy(e.target.value as DeidStrategy)}
                >
                  <option value="counter">Sequential (Person_1)</option>
                  <option value="hash">Hash — global (Person_a3f9c1)</option>
                </select>
              </label>
            )}
            {action === 'redact' && (
              <>
                <label className="field">
                  <span className="field-label">Redaction mode</span>
                  <select value={mode} onChange={(e) => setMode(e.target.value as RedactMode)}>
                    <option value="solid">solid</option>
                    <option value="blur">blur</option>
                    <option value="pixelate">pixelate</option>
                  </select>
                </label>
                <label className="field">
                  <span className="field-label">Fill color</span>
                  <input
                    type="color"
                    value={color}
                    disabled={mode !== 'solid'}
                    onChange={(e) => setColor(e.target.value)}
                  />
                  {mode !== 'solid' && <span className="field-hint">Solid mode only</span>}
                </label>
              </>
            )}
          </div>
          <p className="tab-caption action-hint">
            {action === 'redact' &&
              'Destroys the PII regions — nothing is kept, nothing is recoverable.'}
            {action === 'deidentify' &&
              'Replaces text PII with consistent pseudonyms (Person_1) rendered in place, faces/signatures with placeholders, and returns the mapping for authorized re-linking.'}
            {action === 'anonymize' &&
              'One-way: generalizes ages/dates/geography, collapses names and IDs to tokens, blacks out faces/signatures. No mapping exists.'}
          </p>

          {taxonomy && (
            <details className="exclude-panel">
              <summary>
                Categories to detect ({selected.size}/{totalCategories} selected)
              </summary>
              <div className="select-buttons">
                <button
                  type="button"
                  className="btn-secondary btn-small"
                  onClick={() => setSelected(new Set([...taxonomy.text, ...taxonomy.visual]))}
                >
                  Select all
                </button>
                <button
                  type="button"
                  className="btn-secondary btn-small"
                  onClick={() => setSelected(new Set())}
                >
                  Deselect all
                </button>
              </div>
              <div className="exclude-columns">
                <div>
                  <h3 className="exclude-heading">Text</h3>
                  {taxonomy.text.map((c) => (
                    <label key={c} className="checkbox-row">
                      <input
                        type="checkbox"
                        checked={selected.has(c)}
                        onChange={() => toggle(c)}
                      />
                      {c}
                    </label>
                  ))}
                </div>
                <div>
                  <h3 className="exclude-heading">Visual</h3>
                  {taxonomy.visual.map((c) => (
                    <label key={c} className="checkbox-row">
                      <input
                        type="checkbox"
                        checked={selected.has(c)}
                        onChange={() => toggle(c)}
                      />
                      {c}
                    </label>
                  ))}
                </div>
              </div>
            </details>
          )}
        </section>
      </div>

      <button
        type="button"
        className="primary-btn"
        onClick={run}
        disabled={!file || !ready || loading || selected.size === 0}
      >
        {loading ? 'Working…' : ACTION_BUTTON_LABELS[action]}
      </button>
      {selected.size === 0 && (
        <p className="tab-caption">Select at least one category to run.</p>
      )}

      {error && <div className="error-banner">{error}</div>}

      {detectedEntities.length > 0 && <Legend groups={groups} />}

      {previewUrl && (
        <div className="results-row">
          <section className="card">
            <h2 className="card-title">Detections</h2>
            <canvas ref={canvasRef} className="result-image" />
          </section>
          {redactedUrl && (
            <section className="card">
              <h2 className="card-title">{resultTitle}</h2>
              <img src={redactedUrl} alt={`${resultTitle} result`} className="result-image" />
              <a className="btn-secondary download-btn" href={redactedUrl} download="result.png">
                Download PNG
              </a>
            </section>
          )}
        </div>
      )}

      {mapping && <MappingTable mapping={mapping} />}

      {detectedEntities.length > 0 && (
        <section className="card">
          <h2 className="card-title">Detected entities</h2>
          <div className="table-scroll">
            <table className="entity-table">
              <thead>
                <tr>
                  <th>kind</th>
                  <th>category</th>
                  <th>l1</th>
                  <th>text</th>
                  <th>score</th>
                  <th>bbox</th>
                </tr>
              </thead>
              <tbody>
                {detectedEntities.map((e, i) => (
                  <tr key={i}>
                    <td>{e.kind}</td>
                    <td>{e.category}</td>
                    <td>
                      <span className="l1-dot" style={{ background: l1Rgb(e.l1) }} />
                      {e.l1}
                    </td>
                    <td>{e.text ?? ''}</td>
                    <td>{e.score != null ? e.score.toFixed(3) : ''}</td>
                    <td>{e.bbox.join(', ')}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </section>
      )}
    </div>
  )
}
