import { useState } from 'react'
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
import {
  SortableContext,
  arrayMove,
  sortableKeyboardCoordinates,
  useSortable,
  verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'

import Modal from '../../../components/Modal.jsx'
import { Row } from './NavEditor.jsx'

// The Public tab of Admin → Navigation (THEMING_AND_NAV.md §7, Phase 10).
//
// The public header is the one nav an admin can restructure rather than only
// reorder, so it needs its own editor: a **section is itself an entry in the
// top-level order**, which the fixed coded sections of the admin sidebar never
// are. That is the whole reason this is not the grouped editor with a different
// label — there, groups are a fixed frame and only membership moves.
//
// The tree is `[{kind: 'item' | 'link' | 'section', ...}]`, one level deep, and
// comes from the same `buildPublicNav` the header renders, so what an admin
// drags is what visitors get.

const uid = (prefix) => `${prefix}_${Math.random().toString(36).slice(2, 10)}`

// A path on this site, matching what the server will accept. Checked here so the
// admin gets the message while the field is in front of them; the server's 400
// stays the backstop, not the first feedback.
export function badLinkPath(value) {
  const v = (value || '').trim()
  if (!v) return 'Enter a path.'
  if (/^[a-z][a-z0-9+.-]*:/i.test(v) || v.startsWith('//')) {
    return 'Links must point somewhere on this site — start with “/”.'
  }
  if (!v.startsWith('/')) return 'Start the path with “/”, for example /wiki/new-player-guide.'
  if (/[\s<>"'\\]/.test(v)) return 'A path cannot contain spaces or quotes.'
  if (v.length > 128) return 'That path is too long.'
  return null
}

function SectionCard({ section, index, children, onChange, onDelete }) {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: section.id })
  return (
    <li
      ref={setNodeRef}
      style={{
        transform: CSS.Transform.toString(transform),
        transition,
        listStyle: 'none',
        border: '1px solid var(--line)',
        borderRadius: 'var(--radius-card)',
        background: isDragging ? 'var(--blue)' : 'transparent',
        padding: 10,
      }}
    >
      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <button
          type="button"
          className="sans"
          aria-label={`Reorder ${section.label}`}
          {...attributes}
          {...listeners}
          style={{ border: 'none', background: 'transparent', color: 'var(--dim)', cursor: 'grab', padding: '2px 4px', touchAction: 'none' }}
        >
          <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false">
            <circle cx="9" cy="6" r="1.6" /><circle cx="15" cy="6" r="1.6" />
            <circle cx="9" cy="12" r="1.6" /><circle cx="15" cy="12" r="1.6" />
            <circle cx="9" cy="18" r="1.6" /><circle cx="15" cy="18" r="1.6" />
          </svg>
        </button>
        <input
          className="input"
          value={section.label}
          maxLength={64}
          placeholder="Section name"
          onChange={(e) => onChange({ ...section, label: e.target.value })}
          aria-label={`Name for section ${index + 1}`}
          style={{ flex: '1 1 auto', minWidth: 120, padding: '5px 8px', fontSize: '0.84rem', fontWeight: 600 }}
        />
        <span className="sans dim" style={{ fontSize: '0.7rem' }}>dropdown</span>
        <button
          type="button"
          className="sans"
          title="Delete this section — the entries inside move back out, they are not removed"
          onClick={onDelete}
          style={{
            border: '1px solid var(--line)',
            borderRadius: 'var(--radius-input)',
            background: 'transparent',
            color: 'var(--muted)',
            cursor: 'pointer',
            padding: '4px 8px',
            fontSize: '0.76rem',
          }}
        >
          ×
        </button>
      </div>
      {children}
    </li>
  )
}

export default function PublicNavTree({ tree, onChange }) {
  const [adding, setAdding] = useState(null) // {label, to, error} while the modal is open

  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
  )

  const sections = tree.filter((n) => n.kind === 'section')
  const destinations = [{ value: '', label: 'Top level' }, ...sections.map((s) => ({ value: s.id, label: s.label || 'Section' }))]
  const keyOf = (node) => (node.kind === 'item' ? node.to : node.id)

  // Every mutation rebuilds the tree; there is no partial in-place editing, which
  // keeps "what will be saved" exactly "what is on screen".
  const replace = (nextTree) => onChange(nextTree)

  const updateNode = (key, next) =>
    replace(
      tree.map((node) => {
        if (keyOf(node) === key) return next
        if (node.kind !== 'section') return node
        return { ...node, items: node.items.map((child) => (keyOf(child) === key ? next : child)) }
      }),
    )

  // Moving between containers is the dropdown, not a drag. The entry lands at the
  // end of its destination, where it is visible and can then be dragged home.
  const moveTo = (key, sectionId) => {
    let moving = null
    const stripped = tree
      .map((node) => {
        if (node.kind === 'section') {
          const items = node.items.filter((child) => {
            if (keyOf(child) !== key) return true
            moving = child
            return false
          })
          return { ...node, items }
        }
        if (keyOf(node) === key) {
          moving = node
          return null
        }
        return node
      })
      .filter(Boolean)
    if (!moving) return
    if (!sectionId) return replace([...stripped, moving])
    return replace(
      stripped.map((node) => (node.kind === 'section' && node.id === sectionId ? { ...node, items: [...node.items, moving] } : node)),
    )
  }

  const addSection = () => replace([...tree, { kind: 'section', id: uid('sec'), label: 'New section', items: [] }])

  // Deleting a section must NOT delete what is inside it: those are coded pages
  // and the admin's own links, and losing them to a mis-click would be the one
  // destructive act this screen could commit. They move back to the top level.
  const deleteSection = (id) => {
    const section = tree.find((n) => n.kind === 'section' && n.id === id)
    if (!section) return
    replace([...tree.filter((n) => keyOf(n) !== id), ...(section.items || [])])
  }

  const deleteLink = (id) =>
    replace(
      tree
        .filter((n) => keyOf(n) !== id)
        .map((n) => (n.kind === 'section' ? { ...n, items: n.items.filter((c) => keyOf(c) !== id) } : n)),
    )

  const submitLink = () => {
    const error = badLinkPath(adding.to)
    if (error) return setAdding({ ...adding, error })
    const label = adding.label.trim()
    if (!label) return setAdding({ ...adding, error: 'Give the link a name.' })
    replace([...tree, { kind: 'link', id: uid('lnk'), label, to: adding.to.trim() }])
    return setAdding(null)
  }

  const onDragEnd = (containerId) => (event) => {
    const { active, over } = event
    if (!over || active.id === over.id) return
    if (containerId === null) {
      const from = tree.findIndex((n) => keyOf(n) === active.id)
      const to = tree.findIndex((n) => keyOf(n) === over.id)
      if (from < 0 || to < 0) return
      return replace(arrayMove(tree, from, to))
    }
    return replace(
      tree.map((node) => {
        if (node.kind !== 'section' || node.id !== containerId) return node
        const from = node.items.findIndex((c) => keyOf(c) === active.id)
        const to = node.items.findIndex((c) => keyOf(c) === over.id)
        if (from < 0 || to < 0) return node
        return { ...node, items: arrayMove(node.items, from, to) }
      }),
    )
  }

  const renderRow = (node, sectionId) => (
    <Row
      key={keyOf(node)}
      id={keyOf(node)}
      row={node}
      destinations={destinations}
      destination={sectionId ?? ''}
      onDestination={(value) => moveTo(keyOf(node), value)}
      onChange={(next) => updateNode(keyOf(node), next)}
      onDelete={node.kind === 'link' ? () => deleteLink(node.id) : undefined}
    />
  )

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd(null)}>
        <SortableContext items={tree.map(keyOf)} strategy={verticalListSortingStrategy}>
          <ul style={{ display: 'flex', flexDirection: 'column', gap: 6, margin: 0, padding: 0 }}>
            {tree.map((node, index) =>
              node.kind === 'section' ? (
                <SectionCard
                  key={node.id}
                  section={node}
                  index={index}
                  onChange={(next) => updateNode(node.id, next)}
                  onDelete={() => deleteSection(node.id)}
                >
                  {/* A nested context, so a drag inside a dropdown reorders that
                      dropdown rather than escaping into the header. */}
                  <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd(node.id)}>
                    <SortableContext items={(node.items || []).map(keyOf)} strategy={verticalListSortingStrategy}>
                      <ul style={{ display: 'flex', flexDirection: 'column', gap: 6, margin: '10px 0 0', padding: '0 0 0 22px' }}>
                        {(node.items || []).map((child) => renderRow(child, node.id))}
                        {(node.items || []).length === 0 && (
                          <li className="sans dim" style={{ fontSize: '0.76rem', listStyle: 'none', padding: '4px 2px' }}>
                            Empty — an empty dropdown is not shown on the site.
                          </li>
                        )}
                      </ul>
                    </SortableContext>
                  </DndContext>
                </SectionCard>
              ) : (
                renderRow(node, null)
              ),
            )}
          </ul>
        </SortableContext>
      </DndContext>

      <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
        <button type="button" className="pill" onClick={addSection}>
          + Add dropdown section
        </button>
        <button type="button" className="pill" onClick={() => setAdding({ label: '', to: '', error: null })}>
          + Add link
        </button>
      </div>

      {adding && (
        <Modal
          title="Add a link"
          onClose={() => setAdding(null)}
          width={480}
          footer={
            <>
              <button className="pill" onClick={() => setAdding(null)}>Cancel</button>
              <button className="btn btn-primary btn-sq" onClick={submitLink}>Add link</button>
            </>
          }
        >
          <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
            <label style={{ display: 'block' }}>
              <span className="field-label">Name</span>
              <input
                className="input"
                value={adding.label}
                maxLength={64}
                placeholder="Player Guide"
                onChange={(e) => setAdding({ ...adding, label: e.target.value, error: null })}
              />
            </label>
            <label style={{ display: 'block' }}>
              <span className="field-label">Path on this site</span>
              <input
                className="input"
                value={adding.to}
                maxLength={128}
                placeholder="/wiki/new-player-guide"
                onChange={(e) => setAdding({ ...adding, to: e.target.value, error: null })}
              />
            </label>
            <p className="sans dim" style={{ margin: 0, fontSize: '0.76rem', lineHeight: 1.7 }}>
              Links point somewhere on this site — a wiki page, a custom page, any section of the site.
              They are not gated: the page itself still decides who may open it, so a link to something
              restricted behaves exactly as typing its address would.
            </p>
            {adding.error && <span className="sans" style={{ color: '#d98b84', fontSize: '0.85rem' }}>{adding.error}</span>}
          </div>
        </Modal>
      )}
    </div>
  )
}
