import type { Editor } from '@tiptap/react'
import { useEditorState } from '@tiptap/react'
import { useEffect, useRef, useState } from 'react'
import { uploadImageInto } from '../Editor/EditorCore'

interface Props {
  editor: Editor | null
  // Runs the whole-document voice-consistency pass; `voicing` shows its progress.
  onVoiceCheck: () => void
  voicing: boolean
}

// A formatting button. `active` gets the rose pill treatment so the writer can
// see what's applied at the cursor.
function TBtn({
  onClick,
  active,
  disabled,
  label,
  title,
  children,
}: {
  onClick: () => void
  active?: boolean
  disabled?: boolean
  label: string
  title?: string
  children: React.ReactNode
}) {
  return (
    <button
      type="button"
      aria-label={label}
      title={title ?? label}
      aria-pressed={active}
      disabled={disabled}
      onMouseDown={(e) => e.preventDefault()} // keep editor selection
      onClick={onClick}
      className="flex h-8 min-w-8 items-center justify-center px-2 text-sm font-semibold disabled:opacity-40"
      style={{
        borderRadius: 'var(--radius-input)',
        color: active ? 'var(--color-accent-hover)' : 'var(--color-muted)',
        background: active ? 'var(--color-surface-alt)' : 'transparent',
      }}
    >
      {children}
    </button>
  )
}

const Divider = () => (
  <span className="mx-1 h-5 w-px" style={{ background: 'var(--color-border)' }} />
)

// A popover anchored under its trigger. The trigger + panel share a relative
// wrapper; `open`/`onClose` are owned by the toolbar so only one is open at once.
// A pointer-down outside the wrapper closes it.
function Popover({
  open,
  onClose,
  trigger,
  children,
  width = 200,
}: {
  open: boolean
  onClose: () => void
  trigger: React.ReactNode
  children: React.ReactNode
  width?: number
}) {
  const ref = useRef<HTMLDivElement>(null)
  useEffect(() => {
    if (!open) return
    const onDown = (e: MouseEvent) => {
      if (!ref.current?.contains(e.target as Node)) onClose()
    }
    document.addEventListener('mousedown', onDown)
    return () => document.removeEventListener('mousedown', onDown)
  }, [open, onClose])
  return (
    <div ref={ref} className="relative flex items-center">
      {trigger}
      {open && (
        <div
          className="absolute left-0 top-full z-40 mt-1.5 p-2"
          style={{
            width,
            borderRadius: 'var(--radius-card)',
            background: 'var(--color-surface)',
            border: '1px solid var(--color-border)',
            boxShadow: 'var(--shadow-soft)',
          }}
        >
          {children}
        </div>
      )}
    </div>
  )
}

// Text-color swatches. `null` clears the color back to the theme default.
const TEXT_COLORS: { label: string; value: string | null }[] = [
  { label: 'Default', value: null },
  { label: 'Rose', value: '#D98AAF' },
  { label: 'Coral', value: '#E0785C' },
  { label: 'Honey', value: '#C68B2E' },
  { label: 'Green', value: '#4F9E7F' },
  { label: 'Sky', value: '#4E8FCC' },
  { label: 'Lavender', value: '#7D63C4' },
  { label: 'Plum', value: '#3D2E39' },
]

// Highlighter colors — soft pastels so dark text stays readable on top.
const HIGHLIGHTS: { label: string; value: string | null }[] = [
  { label: 'None', value: null },
  { label: 'Yellow', value: '#FFF1A8' },
  { label: 'Pink', value: '#FAD4E4' },
  { label: 'Mint', value: '#CDEFE2' },
  { label: 'Peach', value: '#FBE0CF' },
  { label: 'Lavender', value: '#E6DCFA' },
  { label: 'Sky', value: '#D6E8FB' },
]

// Font-size presets. `null` clears back to the document default.
const SIZES: { label: string; value: string | null; em: string }[] = [
  { label: 'Small', value: '0.85em', em: '0.85em' },
  { label: 'Normal', value: null, em: '1em' },
  { label: 'Large', value: '1.3em', em: '1.3em' },
  { label: 'Title', value: '1.7em', em: '1.7em' },
]

// A round color chip used in the color/highlight palettes.
function Swatch({
  color,
  active,
  onClick,
  title,
}: {
  color: string | null
  active: boolean
  onClick: () => void
  title: string
}) {
  return (
    <button
      type="button"
      title={title}
      aria-label={title}
      onMouseDown={(e) => e.preventDefault()}
      onClick={onClick}
      className="flex h-7 w-7 items-center justify-center"
      style={{
        borderRadius: 'var(--radius-pill)',
        background: color ?? 'var(--color-surface)',
        border: active ? '2px solid var(--color-accent)' : '1px solid var(--color-border)',
        // The "clear" chip (no color) shows a tiny diagonal stroke.
        backgroundImage: color
          ? undefined
          : 'linear-gradient(135deg, transparent 44%, var(--color-accent) 44%, var(--color-accent) 56%, transparent 56%)',
      }}
    />
  )
}

// Toolbar renders inline formatting controls bound to the live Tiptap editor.
// useEditorState subscribes to just the flags it reads, so the buttons reflect
// the current selection without re-rendering the whole tree on every keystroke.
export function Toolbar({ editor, onVoiceCheck, voicing }: Props) {
  // Which popover (if any) is open. Only one at a time.
  const [menu, setMenu] = useState<'color' | 'highlight' | 'size' | 'link' | 'table' | 'outline' | null>(null)
  const [linkUrl, setLinkUrl] = useState('')
  const fileInputRef = useRef<HTMLInputElement>(null)

  const state = useEditorState({
    editor,
    selector: ({ editor }) =>
      editor
        ? {
            bold: editor.isActive('bold'),
            italic: editor.isActive('italic'),
            underline: editor.isActive('underline'),
            strike: editor.isActive('strike'),
            h1: editor.isActive('heading', { level: 1 }),
            h2: editor.isActive('heading', { level: 2 }),
            h3: editor.isActive('heading', { level: 3 }),
            bullet: editor.isActive('bulletList'),
            ordered: editor.isActive('orderedList'),
            left: editor.isActive({ textAlign: 'left' }),
            center: editor.isActive({ textAlign: 'center' }),
            right: editor.isActive({ textAlign: 'right' }),
            link: editor.isActive('link'),
            inTable: editor.isActive('table'),
            color: editor.getAttributes('textStyle').color ?? null,
            highlight: editor.getAttributes('highlight').color ?? null,
            fontSize: editor.getAttributes('textStyle').fontSize ?? null,
            canUndo: editor.can().undo(),
            canRedo: editor.can().redo(),
          }
        : null,
  })

  if (!editor || !state) return null

  const close = () => setMenu(null)

  // Open the link popover, pre-filling the field with any link already on the
  // selection so it can be edited rather than retyped.
  const openLink = () => {
    setLinkUrl(editor.getAttributes('link').href ?? '')
    setMenu(menu === 'link' ? null : 'link')
  }

  const applyLink = () => {
    const url = linkUrl.trim()
    if (!url) {
      editor.chain().focus().extendMarkRange('link').unsetLink().run()
    } else {
      // Default to https:// when the writer omits a scheme.
      const href = /^(https?:|mailto:|\/)/i.test(url) ? url : `https://${url}`
      editor.chain().focus().extendMarkRange('link').setLink({ href }).run()
    }
    close()
  }

  // Collect the document's headings (with their positions) for the outline. Read
  // fresh each time the popover opens, so it always reflects the current doc.
  const headings: { level: number; text: string; pos: number }[] = []
  if (menu === 'outline') {
    editor.state.doc.descendants((node, pos) => {
      if (node.type.name === 'heading') {
        headings.push({ level: (node.attrs.level as number) || 1, text: node.textContent || '（无标题）', pos })
      }
    })
  }

  // Scroll a heading into view without moving the selection (which would pop the
  // rewrite bubble). domAtPos resolves the heading's DOM node to scroll to.
  const gotoHeading = (pos: number) => {
    const dom = editor.view.domAtPos(pos + 1)
    const el = dom.node.nodeType === Node.TEXT_NODE ? dom.node.parentElement : (dom.node as HTMLElement)
    el?.scrollIntoView({ block: 'start', behavior: 'smooth' })
    close()
  }

  const onPickImage = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0]
    if (file) uploadImageInto(editor.view, file)
    e.target.value = '' // allow re-picking the same file
  }

  return (
    <div
      className="petal-toolbar mb-4 flex items-center gap-0.5 self-start px-2 py-1.5"
      style={{
        borderRadius: 'var(--radius-card)',
        background: 'var(--color-surface)',
        boxShadow: 'var(--shadow-soft)',
      }}
    >
      <TBtn label="Undo" disabled={!state.canUndo} onClick={() => editor.chain().focus().undo().run()}>
        ↶
      </TBtn>
      <TBtn label="Redo" disabled={!state.canRedo} onClick={() => editor.chain().focus().redo().run()}>
        ↷
      </TBtn>
      <Divider />

      <TBtn label="Bold" active={state.bold} onClick={() => editor.chain().focus().toggleBold().run()}>
        <span className="font-extrabold">B</span>
      </TBtn>
      <TBtn label="Italic" active={state.italic} onClick={() => editor.chain().focus().toggleItalic().run()}>
        <span className="italic">I</span>
      </TBtn>
      <TBtn
        label="Underline"
        active={state.underline}
        onClick={() => editor.chain().focus().toggleUnderline().run()}
      >
        <span className="underline">U</span>
      </TBtn>
      <TBtn label="Strikethrough" active={state.strike} onClick={() => editor.chain().focus().toggleStrike().run()}>
        <span className="line-through">S</span>
      </TBtn>
      <Divider />

      {/* Text color */}
      <Popover
        open={menu === 'color'}
        onClose={close}
        width={188}
        trigger={
          <TBtn label="Text color" active={menu === 'color'} onClick={() => setMenu(menu === 'color' ? null : 'color')}>
            <span className="flex flex-col items-center leading-none">
              <span className="text-sm font-bold">A</span>
              <span
                className="mt-0.5 h-1 w-4 rounded-full"
                style={{ background: state.color ?? 'var(--color-accent)' }}
              />
            </span>
          </TBtn>
        }
      >
        <div className="grid grid-cols-4 gap-1.5">
          {TEXT_COLORS.map((c) => (
            <Swatch
              key={c.label}
              color={c.value}
              title={c.label}
              active={state.color === c.value}
              onClick={() => {
                if (c.value) editor.chain().focus().setColor(c.value).run()
                else editor.chain().focus().unsetColor().run()
                close()
              }}
            />
          ))}
        </div>
      </Popover>

      {/* Highlight */}
      <Popover
        open={menu === 'highlight'}
        onClose={close}
        width={170}
        trigger={
          <TBtn
            label="Highlight"
            active={menu === 'highlight' || !!state.highlight}
            onClick={() => setMenu(menu === 'highlight' ? null : 'highlight')}
          >
            <span
              className="flex h-5 w-5 items-center justify-center rounded text-xs font-bold"
              style={{ background: state.highlight ?? '#FFF1A8', color: 'var(--color-plum)' }}
            >
              H
            </span>
          </TBtn>
        }
      >
        <div className="grid grid-cols-4 gap-1.5">
          {HIGHLIGHTS.map((c) => (
            <Swatch
              key={c.label}
              color={c.value}
              title={c.label}
              active={state.highlight === c.value}
              onClick={() => {
                if (c.value) editor.chain().focus().setHighlight({ color: c.value }).run()
                else editor.chain().focus().unsetHighlight().run()
                close()
              }}
            />
          ))}
        </div>
      </Popover>

      {/* Font size */}
      <Popover
        open={menu === 'size'}
        onClose={close}
        width={140}
        trigger={
          <TBtn label="Text size" active={menu === 'size'} onClick={() => setMenu(menu === 'size' ? null : 'size')}>
            <span className="text-xs font-bold tracking-tight">
              <span className="text-[0.7rem]">A</span>
              <span className="text-sm">A</span>
            </span>
          </TBtn>
        }
      >
        <div className="flex flex-col gap-0.5">
          {SIZES.map((s) => {
            const active = state.fontSize === s.value
            return (
              <button
                key={s.label}
                type="button"
                onMouseDown={(e) => e.preventDefault()}
                onClick={() => {
                  if (s.value) editor.chain().focus().setFontSize(s.value).run()
                  else editor.chain().focus().unsetFontSize().run()
                  close()
                }}
                className="flex items-center justify-between rounded px-2 py-1 text-left"
                style={{
                  background: active ? 'var(--color-surface-alt)' : 'transparent',
                  color: active ? 'var(--color-accent-hover)' : 'var(--color-plum)',
                }}
              >
                <span style={{ fontSize: s.em }}>{s.label}</span>
              </button>
            )
          })}
        </div>
      </Popover>
      <Divider />

      <TBtn label="Heading 1" active={state.h1} onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}>
        H1
      </TBtn>
      <TBtn label="Heading 2" active={state.h2} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}>
        H2
      </TBtn>
      <TBtn label="Heading 3" active={state.h3} onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}>
        H3
      </TBtn>
      <TBtn label="Bullet list" active={state.bullet} onClick={() => editor.chain().focus().toggleBulletList().run()}>
        •
      </TBtn>
      <TBtn label="Numbered list" active={state.ordered} onClick={() => editor.chain().focus().toggleOrderedList().run()}>
        1.
      </TBtn>
      <Divider />

      <TBtn label="Align left" active={state.left} onClick={() => editor.chain().focus().setTextAlign('left').run()}>
        ⇤
      </TBtn>
      <TBtn label="Align center" active={state.center} onClick={() => editor.chain().focus().setTextAlign('center').run()}>
        ↔
      </TBtn>
      <TBtn label="Align right" active={state.right} onClick={() => editor.chain().focus().setTextAlign('right').run()}>
        ⇥
      </TBtn>
      <Divider />

      {/* Link */}
      <Popover
        open={menu === 'link'}
        onClose={close}
        width={236}
        trigger={
          <TBtn label="Link" active={state.link || menu === 'link'} onClick={openLink}>
            🔗
          </TBtn>
        }
      >
        <div className="flex flex-col gap-2">
          <input
            autoFocus
            value={linkUrl}
            onChange={(e) => setLinkUrl(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === 'Enter') applyLink()
              if (e.key === 'Escape') close()
            }}
            placeholder="https://…"
            className="w-full px-2 py-1.5 text-sm focus:outline-none"
            style={{
              borderRadius: 'var(--radius-input)',
              border: '1px solid var(--color-border)',
              color: 'var(--color-plum)',
            }}
          />
          <div className="flex items-center justify-between">
            {state.link ? (
              <button
                type="button"
                onMouseDown={(e) => e.preventDefault()}
                onClick={() => {
                  editor.chain().focus().extendMarkRange('link').unsetLink().run()
                  close()
                }}
                className="px-2 py-1 text-xs font-semibold"
                style={{ color: 'var(--color-muted)' }}
              >
                Remove
              </button>
            ) : (
              <span />
            )}
            <button
              type="button"
              onMouseDown={(e) => e.preventDefault()}
              onClick={applyLink}
              className="px-3 py-1 text-xs font-bold"
              style={{
                borderRadius: 'var(--radius-pill)',
                background: 'var(--color-accent)',
                color: '#fff',
              }}
            >
              Apply
            </button>
          </div>
        </div>
      </Popover>

      {/* Image */}
      <input ref={fileInputRef} type="file" accept="image/*" hidden onChange={onPickImage} />
      <TBtn label="Insert image" onClick={() => fileInputRef.current?.click()}>
        🖼️
      </TBtn>

      {/* Table */}
      <Popover
        open={menu === 'table'}
        onClose={close}
        width={state.inTable ? 188 : 150}
        trigger={
          <TBtn label="Table" active={state.inTable || menu === 'table'} onClick={() => setMenu(menu === 'table' ? null : 'table')}>
            ▦
          </TBtn>
        }
      >
        {state.inTable ? (
          <div className="flex flex-col gap-0.5 text-sm" style={{ color: 'var(--color-plum)' }}>
            <TableAction label="Add row below" onClick={() => editor.chain().focus().addRowAfter().run()} />
            <TableAction label="Add column right" onClick={() => editor.chain().focus().addColumnAfter().run()} />
            <TableAction label="Toggle header row" onClick={() => editor.chain().focus().toggleHeaderRow().run()} />
            <TableAction label="Delete row" onClick={() => editor.chain().focus().deleteRow().run()} />
            <TableAction label="Delete column" onClick={() => editor.chain().focus().deleteColumn().run()} />
            <TableAction
              label="Delete table"
              danger
              onClick={() => {
                editor.chain().focus().deleteTable().run()
                close()
              }}
            />
          </div>
        ) : (
          <GridPicker
            onPick={(rows, cols) => {
              editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run()
              close()
            }}
          />
        )}
      </Popover>

      {/* Outline / document map */}
      <Popover
        open={menu === 'outline'}
        onClose={close}
        width={240}
        trigger={
          <TBtn label="Outline" active={menu === 'outline'} onClick={() => setMenu(menu === 'outline' ? null : 'outline')}>
            ☰
          </TBtn>
        }
      >
        <div className="max-h-72 overflow-y-auto">
          <p className="mb-1.5 px-1 text-xs font-bold" style={{ color: 'var(--color-muted)' }}>
            大纲 · Outline
          </p>
          {headings.length === 0 ? (
            <p className="px-1 py-2 text-xs leading-snug" style={{ color: 'var(--color-muted)' }}>
              用 H1/H2/H3 添加标题，这里就会出现导航。<br />
              Add headings to navigate them here.
            </p>
          ) : (
            <div className="flex flex-col">
              {headings.map((h, i) => (
                <button
                  key={i}
                  type="button"
                  onMouseDown={(e) => e.preventDefault()}
                  onClick={() => gotoHeading(h.pos)}
                  className="truncate rounded px-1.5 py-1 text-left text-sm hover:bg-[var(--color-surface-alt)]"
                  style={{
                    paddingLeft: `${(h.level - 1) * 12 + 6}px`,
                    color: 'var(--color-plum)',
                    fontWeight: h.level === 1 ? 700 : 500,
                  }}
                  title={h.text}
                >
                  {h.text}
                </button>
              ))}
            </div>
          )}
        </div>
      </Popover>
      <Divider />

      <button
        type="button"
        aria-label="Check my voice"
        disabled={voicing}
        onMouseDown={(e) => e.preventDefault()} // keep editor selection
        onClick={onVoiceCheck}
        className="ml-0.5 inline-flex h-8 items-center gap-1.5 whitespace-nowrap px-3 text-xs font-bold transition-colors disabled:opacity-70"
        style={{
          borderRadius: 'var(--radius-pill)',
          color: 'var(--color-plum)',
          background: 'var(--color-honey)',
        }}
        title="Read the whole document for passages that don't sound like you"
      >
        <span
          className={voicing ? 'petal-checkpoint-dot inline-block h-2 w-2 rounded-full' : 'hidden'}
          style={{ background: 'var(--color-plum)' }}
          aria-hidden
        />
        {voicing ? 'Reading…' : 'Check my voice 🍯'}
      </button>
    </div>
  )
}

// A row in the in-table editing menu.
function TableAction({ label, onClick, danger }: { label: string; onClick: () => void; danger?: boolean }) {
  return (
    <button
      type="button"
      onMouseDown={(e) => e.preventDefault()}
      onClick={onClick}
      className="rounded px-2 py-1 text-left"
      style={{ color: danger ? 'var(--color-accent-hover)' : 'var(--color-plum)' }}
    >
      {label}
    </button>
  )
}

// A hover-to-size grid for inserting a table. Hovering a cell highlights the
// rectangle from the top-left; clicking inserts that many rows × columns.
function GridPicker({ onPick }: { onPick: (rows: number, cols: number) => void }) {
  const MAX = 6
  const [hover, setHover] = useState<{ r: number; c: number }>({ r: 0, c: 0 })
  return (
    <div>
      <div
        className="grid gap-0.5"
        style={{ gridTemplateColumns: `repeat(${MAX}, 1fr)` }}
        onMouseLeave={() => setHover({ r: 0, c: 0 })}
      >
        {Array.from({ length: MAX * MAX }).map((_, i) => {
          const r = Math.floor(i / MAX) + 1
          const c = (i % MAX) + 1
          const on = r <= hover.r && c <= hover.c
          return (
            <button
              key={i}
              type="button"
              onMouseDown={(e) => e.preventDefault()}
              onMouseEnter={() => setHover({ r, c })}
              onClick={() => onPick(r, c)}
              className="h-4 w-4"
              style={{
                borderRadius: 3,
                background: on ? 'var(--color-accent)' : 'var(--color-surface-alt)',
                border: '1px solid var(--color-border)',
              }}
            />
          )
        })}
      </div>
      <div className="mt-1.5 text-center text-xs" style={{ color: 'var(--color-muted)' }}>
        {hover.r > 0 ? `${hover.r} × ${hover.c}` : 'Pick a size'}
      </div>
    </div>
  )
}
