import { useEffect, useMemo, useState, type ReactNode } from 'react'
import type {
  BlockAttributeValue,
  BlockDocument,
  BlockFieldGroupValue,
  BlockNode,
  FieldBlockDefinition,
  FieldGroup,
  InlineBlockNode,
  ResolvedFieldBlock,
  RuntimeFieldValueMap,
} from '@platform-modules/fields'
import { CompleteFieldValuesEditor, type CompleteFieldEditorAdapters, type FieldValidationIssue } from './complete-editor.js'

const ALIGNMENTS = ['left','center','right','wide','full'] as const
const SUPPORT_KEYS = ['anchor','className','multiple','reusable','innerBlocks','jsx'] as const

function uniqueList(raw:string):readonly string[]{return Object.freeze([...new Set(raw.split(',').map((item)=>item.trim()).filter(Boolean))])}

function JsonEditor({label,value,onChange,disabled}:{label:string;value:unknown;onChange:(value:unknown)=>void;disabled?:boolean}){
  const serialized=useMemo(()=>JSON.stringify(value??{},null,2),[value]),[draft,setDraft]=useState(serialized),[error,setError]=useState<string|null>(null)
  useEffect(()=>setDraft(serialized),[serialized])
  return <div><label>{label}<textarea rows={6} value={draft} disabled={disabled} onChange={(event)=>{const next=event.currentTarget.value;setDraft(next);try{const parsed=JSON.parse(next) as unknown;setError(null);onChange(parsed)}catch(cause){setError(cause instanceof Error?cause.message:String(cause))}}}/></label>{error?<div role="alert" tabIndex={0}>{error}</div>:null}</div>
}

export interface FieldBlockDefinitionEditorProps {
  readonly value: FieldBlockDefinition
  readonly onChange: (value: FieldBlockDefinition) => void
  readonly availableGroupKeys?: readonly string[]
  readonly availableBlockKeys?: readonly string[]
  readonly disabled?: boolean
}

export function FieldBlockDefinitionEditor({value,onChange,availableGroupKeys=[],availableBlockKeys=[],disabled}:FieldBlockDefinitionEditorProps){
  const update=(patch:Partial<FieldBlockDefinition>)=>onChange({...value,...patch})
  const support=value.supports??{}
  const issues:string[]=[]
  if(!/^[a-z][a-z0-9_-]*$/.test(value.key))issues.push('Block key must be normalized')
  if(!value.title.trim())issues.push('Block title is required')
  return <section className="fields-block-definition" style={{containerType:'inline-size',containerName:'fields-block-definition'}} aria-label={`Block ${value.title||value.key}`}>
    <style>{`.fields-block-definition__grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.65rem}.fields-block-definition__wide{grid-column:1/-1}.fields-block-definition__checks{display:flex;gap:.7rem;flex-wrap:wrap}@container fields-block-definition (max-width:36rem){.fields-block-definition__grid{grid-template-columns:minmax(0,1fr)}.fields-block-definition__wide{grid-column:1}}`}</style>
    <fieldset disabled={disabled}><legend>Block definition</legend>
      <div className="fields-block-definition__grid">
        <label>Title<input aria-label="Block title" value={value.title} onChange={(event)=>update({title:event.currentTarget.value})}/></label>
        <label>Key<input aria-label="Block key" value={value.key} onChange={(event)=>update({key:event.currentTarget.value})}/></label>
        <label>Category<input aria-label="Block category" value={value.category??''} onChange={(event)=>update({category:event.currentTarget.value||undefined})}/></label>
        <label>Icon<input aria-label="Block icon" value={value.icon??''} onChange={(event)=>update({icon:event.currentTarget.value||undefined})}/></label>
        <label>Mode<select aria-label="Block mode" value={value.mode??'auto'} onChange={(event)=>update({mode:event.currentTarget.value as NonNullable<FieldBlockDefinition['mode']>})}><option value="auto">auto</option><option value="preview">preview</option><option value="edit">edit</option></select></label>
        <label>Template lock<select aria-label="Block template lock" value={value.templateLock===false?'false':value.templateLock??'false'} onChange={(event)=>update({templateLock:event.currentTarget.value==='false'?false:event.currentTarget.value as Exclude<FieldBlockDefinition['templateLock'],false|undefined>})}><option value="false">unlocked</option><option value="insert">insert</option><option value="all">all</option><option value="contentOnly">contentOnly</option></select></label>
        <label className="fields-block-definition__wide">Description<textarea aria-label="Block description" value={value.description??''} onChange={(event)=>update({description:event.currentTarget.value||undefined})}/></label>
        <label className="fields-block-definition__wide">Keywords<input aria-label="Block keywords" value={(value.keywords??[]).join(', ')} onChange={(event)=>update({keywords:uniqueList(event.currentTarget.value)})}/></label>
        <label className="fields-block-definition__wide">Parent blocks<input aria-label="Block parents" value={(value.parent??[]).join(', ')} onChange={(event)=>update({parent:uniqueList(event.currentTarget.value)})}/></label>
        <label className="fields-block-definition__wide">Ancestor blocks<input aria-label="Block ancestors" value={(value.ancestor??[]).join(', ')} onChange={(event)=>update({ancestor:uniqueList(event.currentTarget.value)})}/></label>
      </div>
      <fieldset><legend>Field groups</legend><div className="fields-block-definition__checks">{availableGroupKeys.map((key)=><label key={key}><input type="checkbox" checked={value.fieldGroupKeys.includes(key)} onChange={(event)=>update({fieldGroupKeys:event.currentTarget.checked?[...value.fieldGroupKeys,key]:value.fieldGroupKeys.filter((candidate)=>candidate!==key)})}/>{key}</label>)}</div></fieldset>
      <fieldset><legend>Alignment</legend><div className="fields-block-definition__checks">{ALIGNMENTS.map((align)=><label key={align}><input type="checkbox" checked={(value.align??[]).includes(align)} onChange={(event)=>update({align:event.currentTarget.checked?[...(value.align??[]),align]:(value.align??[]).filter((candidate)=>candidate!==align)})}/>{align}</label>)}</div></fieldset>
      <fieldset><legend>Supports</legend><div className="fields-block-definition__checks">{SUPPORT_KEYS.map((key)=><label key={key}><input type="checkbox" checked={support[key]===true} onChange={(event)=>update({supports:{...support,[key]:event.currentTarget.checked||undefined}})}/>{key}</label>)}</div></fieldset>
      {availableBlockKeys.length?<div role="note">Available block keys: {availableBlockKeys.join(', ')}</div>:null}
      <JsonEditor label="Template JSON" value={value.template??[]} disabled={disabled} onChange={(template)=>update({template:Array.isArray(template)?template as FieldBlockDefinition['template']:undefined})}/>
      {issues.length?<div role="alert" tabIndex={0}>{issues.join('. ')}</div>:null}
    </fieldset>
  </section>
}

export interface BlockDefinitionBinding { readonly block: FieldBlockDefinition | ResolvedFieldBlock; readonly revision: number }
export interface BlockFieldGroupBinding { readonly group: FieldGroup; readonly revision: number }
export interface BlockDocumentEditorAdapters extends CompleteFieldEditorAdapters { readonly createNodeId?: () => string }
export interface BlockDocumentEditorProps {
  readonly value: BlockDocument
  readonly onChange: (value: BlockDocument) => void
  readonly blocks: readonly BlockDefinitionBinding[]
  readonly groups: readonly BlockFieldGroupBinding[]
  readonly errors?: Readonly<Record<string,readonly FieldValidationIssue[]>>
  readonly disabled?: boolean
  readonly readOnly?: boolean
  readonly adapters?: BlockDocumentEditorAdapters
}

function createId():string{return typeof crypto!=='undefined'&&typeof crypto.randomUUID==='function'?crypto.randomUUID():`block-${Date.now()}-${Math.random().toString(36).slice(2)}`}
function latestGroupBinding(bindings:readonly BlockFieldGroupBinding[],key:string){return bindings.filter((binding)=>binding.group.key===key).sort((a,b)=>b.revision-a.revision)[0]}
function exactGroupBinding(bindings:readonly BlockFieldGroupBinding[],key:string,revision:number){return bindings.find((binding)=>binding.group.key===key&&binding.revision===revision)}
function latestBlockBinding(bindings:readonly BlockDefinitionBinding[],key:string){return bindings.filter((binding)=>binding.block.key===key).sort((a,b)=>b.revision-a.revision)[0]}
function exactBlockBinding(bindings:readonly BlockDefinitionBinding[],key:string,revision:number){return bindings.find((binding)=>binding.block.key===key&&binding.revision===revision)}
function latestBlockBindings(bindings:readonly BlockDefinitionBinding[]):readonly BlockDefinitionBinding[]{const keys=[...new Set(bindings.map((binding)=>binding.block.key))];return Object.freeze(keys.map((key)=>latestBlockBinding(bindings,key)!).filter(Boolean))}
function canCreateInline(binding:BlockDefinitionBinding,groups:readonly BlockFieldGroupBinding[]):boolean{return binding.block.fieldGroupKeys.every((key)=>latestGroupBinding(groups,key)!==undefined)}
function newInline(binding:BlockDefinitionBinding,groups:readonly BlockFieldGroupBinding[],id:string):InlineBlockNode{return {kind:'inline',id,type:binding.block.key,blockDefinitionRevision:binding.revision,fieldGroups:binding.block.fieldGroupKeys.map((key)=>{const group=latestGroupBinding(groups,key);if(!group)throw new Error(`Missing field group binding ${key}`);return {groupKey:key,definitionRevision:group.revision,values:{}}})}}

function BlockNodeList({nodes,onChange,blocks,groups,errors,disabled,readOnly,adapters,path,announce,scopeLabel}:{nodes:readonly BlockNode[];onChange:(nodes:readonly BlockNode[])=>void;blocks:readonly BlockDefinitionBinding[];groups:readonly BlockFieldGroupBinding[];errors:Readonly<Record<string,readonly FieldValidationIssue[]>>;disabled:boolean;readOnly:boolean;adapters:BlockDocumentEditorAdapters;path:readonly (string|number)[];announce:(message:string)=>void;scopeLabel:string}){
  const locked=disabled||readOnly
  const replace=(index:number,node:BlockNode)=>onChange(nodes.map((candidate,candidateIndex)=>candidateIndex===index?node:candidate))
  const move=(index:number,direction:-1|1)=>{const target=index+direction;if(target<0||target>=nodes.length)return;const next=[...nodes];[next[index],next[target]]=[next[target]!,next[index]!];onChange(next);announce(`Block moved ${direction<0?'up':'down'}`)}
  return <div className="fields-block-document__list">
    {nodes.map((node,index)=><article key={node.id} className="fields-block-document__node" aria-label={`Block ${index+1}`}>
      {node.kind==='reusable'?<div className="fields-block-document__grid"><strong>Reusable block</strong><label>Reusable ID<input aria-label={`Block ${index+1} reusable id`} disabled={locked} value={node.reusableId} onChange={(event)=>replace(index,{...node,reusableId:event.currentTarget.value})}/></label></div>:<InlineNodeEditor node={node} onChange={(next)=>replace(index,next)} blocks={blocks} groups={groups} errors={errors} disabled={disabled} readOnly={readOnly} adapters={adapters} path={[...path,node.id]} announce={announce}/>}
      <div className="fields-block-document__actions">
        <button type="button" aria-label={`Move block ${index+1} up`} aria-disabled={index===0||locked} disabled={locked} onClick={()=>{if(index>0)move(index,-1)}}>Move up</button>
        <button type="button" aria-label={`Move block ${index+1} down`} aria-disabled={index===nodes.length-1||locked} disabled={locked} onClick={()=>{if(index<nodes.length-1)move(index,1)}}>Move down</button>
        <button type="button" aria-label={`Remove block ${index+1}`} disabled={locked} onClick={()=>{onChange(nodes.filter((_,candidateIndex)=>candidateIndex!==index));announce('Block removed')}}>Remove</button>
      </div>
    </article>)}
    <div className="fields-block-document__actions">
      {latestBlockBindings(blocks).map((binding)=>{const missingGroups=binding.block.fieldGroupKeys.filter((key)=>!latestGroupBinding(groups,key));return <span key={binding.block.key}>{missingGroups.length?<span role="alert" className="sr-only">Cannot add {binding.block.title}: missing field groups {missingGroups.join(', ')}</span>:null}<button type="button" aria-label={`Add ${binding.block.title} to ${scopeLabel}`} disabled={locked||missingGroups.length>0} onClick={()=>{if(missingGroups.length)return;onChange([...nodes,newInline(binding,groups,(adapters.createNodeId??createId)())]);announce(`${binding.block.title} block added`)}}>Add {binding.block.title}</button></span>})}
      <button type="button" aria-label={`Add reusable block to ${scopeLabel}`} disabled={locked} onClick={()=>{onChange([...nodes,{kind:'reusable',id:(adapters.createNodeId??createId)(),reusableId:''}]);announce('Reusable block added')}}>Add reusable block</button>
    </div>
  </div>
}

function InlineNodeEditor({node,onChange,blocks,groups,errors,disabled,readOnly,adapters,path,announce}:{node:InlineBlockNode;onChange:(node:InlineBlockNode)=>void;blocks:readonly BlockDefinitionBinding[];groups:readonly BlockFieldGroupBinding[];errors:Readonly<Record<string,readonly FieldValidationIssue[]>>;disabled:boolean;readOnly:boolean;adapters:BlockDocumentEditorAdapters;path:readonly (string|number)[];announce:(message:string)=>void}){
  const binding=exactBlockBinding(blocks,node.type,node.blockDefinitionRevision),definition=binding?.block,locked=disabled||readOnly
  const fieldGroups=new Map(node.fieldGroups.map((group)=>[group.groupKey,group]))
  const updateGroup=(groupKey:string,next:RuntimeFieldValueMap)=>{const required=definition?.fieldGroupKeys??[];const nextGroups:BlockFieldGroupValue[]=required.map((key)=>{const existing=fieldGroups.get(key),group=existing?exactGroupBinding(groups,key,existing.definitionRevision):latestGroupBinding(groups,key);if(!group)throw new Error(`Missing field group binding ${key}${existing?`@${existing.definitionRevision}`:''}`);return {groupKey:key,definitionRevision:existing?.definitionRevision??group.revision,values:key===groupKey?next:existing?.values??{}}});onChange({...node,fieldGroups:nextGroups})}
  const changeType=(key:string)=>{const nextBinding=latestBlockBinding(blocks,key);if(!nextBinding||!canCreateInline(nextBinding,groups))return;onChange({...newInline(nextBinding,groups,node.id),...(node.attributes?{attributes:node.attributes}:{}),...(node.children?{children:node.children}:{})})}
  return <div className="fields-block-document__inline">
    <div className="fields-block-document__grid"><label>Block type<select aria-label={`Block ${node.id} type`} disabled={locked} value={node.type} onChange={(event)=>changeType(event.currentTarget.value)}>{latestBlockBindings(blocks).map((candidate)=><option key={candidate.block.key} value={candidate.block.key} disabled={!canCreateInline(candidate,groups)}>{candidate.block.title}</option>)}</select></label><span>Revision {node.blockDefinitionRevision}</span></div>
    {!definition?<div role="alert" tabIndex={0}>Unavailable block definition {node.type}@{node.blockDefinitionRevision}</div>:definition.fieldGroupKeys.map((key)=>{const current=fieldGroups.get(key),group=current?exactGroupBinding(groups,key,current.definitionRevision):latestGroupBinding(groups,key);return <fieldset key={key} disabled={disabled}><legend>{group?.group.title??key}</legend>{group?<CompleteFieldValuesEditor fields={group.group.fields} value={current?.values??{}} onChange={(next)=>updateGroup(key,next)} errors={errors[`${node.id}:${key}`]??[]} disabled={disabled} readOnly={readOnly} adapters={adapters} path={[...path,key]} idPrefix={`block-${node.id}-${key}`}/>:<div role="alert" tabIndex={0}>Unavailable field group {key}{current?`@${current.definitionRevision}`:''}</div>}</fieldset>})}
    <JsonEditor label="Block attributes JSON" value={node.attributes??{}} disabled={locked} onChange={(attributes)=>onChange({...node,attributes:attributes&&typeof attributes==='object'&&!Array.isArray(attributes)?attributes as Readonly<Record<string,BlockAttributeValue>>:undefined})}/>
    {definition?.supports?.innerBlocks?<fieldset disabled={disabled}><legend>Inner blocks</legend><BlockNodeList nodes={node.children??[]} onChange={(children)=>onChange({...node,children})} blocks={blocks} groups={groups} errors={errors} disabled={disabled} readOnly={readOnly} adapters={adapters} path={[...path,'children']} announce={announce} scopeLabel={`inner blocks of ${node.id}`}/></fieldset>:null}
  </div>
}

export function BlockDocumentEditor({value,onChange,blocks,groups,errors={},disabled=false,readOnly=false,adapters={}}:BlockDocumentEditorProps){
  const [announcement,setAnnouncement]=useState('')
  return <section className="fields-block-document" style={{containerType:'inline-size',containerName:'fields-block-document'}} aria-label="Block document editor">
    <style>{`.fields-block-document__list{display:grid;gap:.8rem}.fields-block-document__node{border:1px solid currentColor;border-radius:.35rem;padding:.75rem;display:grid;gap:.6rem}.fields-block-document__grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.65rem}.fields-block-document__actions{display:flex;gap:.4rem;flex-wrap:wrap}@container fields-block-document (max-width:36rem){.fields-block-document__grid{grid-template-columns:minmax(0,1fr)}}`}</style>
    <div role="status" aria-label="Block document status" aria-live="polite" aria-atomic="true" style={{position:'absolute',width:1,height:1,overflow:'hidden',clip:'rect(0 0 0 0)'}}>{announcement}</div>
    <BlockNodeList nodes={value.roots} onChange={(roots)=>onChange({version:1,roots})} blocks={blocks} groups={groups} errors={errors} disabled={disabled} readOnly={readOnly} adapters={adapters} path={['roots']} announce={setAnnouncement} scopeLabel="document"/>
  </section>
}

export interface BlockPreviewAdapter {
  readonly renderInline:(input:{node:InlineBlockNode;binding:BlockDefinitionBinding;children:ReactNode})=>ReactNode
  readonly renderReusable:(input:{node:Extract<BlockNode,{kind:'reusable'}>})=>ReactNode
}
export interface BlockDocumentPreviewProps {
  readonly document:BlockDocument
  readonly blocks:readonly BlockDefinitionBinding[]
  readonly adapter:BlockPreviewAdapter
}
function PreviewNodes({nodes,blocks,adapter}:{nodes:readonly BlockNode[];blocks:readonly BlockDefinitionBinding[];adapter:BlockPreviewAdapter}){
  return <>{nodes.map((node)=>{
    if(node.kind==='reusable')return <div key={node.id} data-preview-node={node.id}>{adapter.renderReusable({node})}</div>
    const binding=exactBlockBinding(blocks,node.type,node.blockDefinitionRevision)
    if(!binding||binding.revision!==node.blockDefinitionRevision)return <div key={node.id} role="alert" tabIndex={0}>Unavailable block definition {node.type}@{node.blockDefinitionRevision}</div>
    const children=<PreviewNodes nodes={node.children??[]} blocks={blocks} adapter={adapter}/>
    return <div key={node.id} data-preview-node={node.id}>{adapter.renderInline({node,binding,children})}</div>
  })}</>
}
export function BlockDocumentPreview({document,blocks,adapter}:BlockDocumentPreviewProps){return <section className="fields-block-preview" style={{containerType:'inline-size'}} aria-label="Block document preview"><PreviewNodes nodes={document.roots} blocks={blocks} adapter={adapter}/></section>}
