import type {
  OrganizationModel,
  OrganizationModelAction,
  OrganizationModelActionInvocation,
  OrganizationModelAgentResourceEntry
} from '../organization-model/types'
import { getSystem } from '../organization-model/helpers'
import type { listAllResources, listAllRoles, listAllSystemsFlat, OmSearchHitKind } from './queries'

export const OM_SEARCH_HIT_KINDS: OmSearchHitKind[] = ['system', 'resource', 'knowledge', 'ontology', 'role', 'policy']

export function parseKnowledgeSearchLimit(raw: string | undefined, commandName = 'knowledge:search'): number {
  if (raw === undefined) return 10
  const n = Number.parseInt(raw, 10)
  if (Number.isNaN(n) || n < 0) {
    throw new Error(`${commandName}: --limit must be a non-negative integer (got "${raw}")`)
  }
  return n
}

export function parseKnowledgeSearchKinds(
  raw: string | undefined,
  commandName = 'knowledge:search'
): OmSearchHitKind[] | undefined {
  if (raw === undefined) return undefined
  const requested = raw
    .split(',')
    .map((s) => s.trim().toLowerCase())
    .filter((s) => s.length > 0)
  const valid: OmSearchHitKind[] = []
  const invalid: string[] = []
  for (const kind of requested) {
    if (OM_SEARCH_HIT_KINDS.includes(kind as OmSearchHitKind)) {
      valid.push(kind as OmSearchHitKind)
    } else {
      invalid.push(kind)
    }
  }
  if (invalid.length > 0) {
    throw new Error(`${commandName}: unknown kind(s) "${invalid.join(', ')}". Valid: ${OM_SEARCH_HIT_KINDS.join(', ')}`)
  }
  return valid.length > 0 ? valid : undefined
}

export function formatKnowledgeSystemsList(entries: ReturnType<typeof listAllSystemsFlat>): string {
  if (entries.length === 0) return '(no results)'
  const pathWidth = Math.max(...entries.map((e) => e.path.length), 4)
  const header = `${'PATH'.padEnd(pathWidth)}  LABEL`
  const divider = '-'.repeat(header.length + 20)
  const rows = entries.map((e) => {
    const label = e.system.label ?? e.system.title ?? e.path
    return `${e.path.padEnd(pathWidth)}  ${label}`
  })
  return [header, divider, ...rows].join('\n')
}

export function formatKnowledgeResourcesList(resources: ReturnType<typeof listAllResources>): string {
  if (resources.length === 0) return '(no results)'
  const idWidth = Math.max(...resources.map((r) => r.id.length), 4)
  const kindWidth = Math.max(...resources.map((r) => r.kind.length), 4)
  const header = `${'ID'.padEnd(idWidth)}  ${'KIND'.padEnd(kindWidth)}  TITLE`
  const divider = '-'.repeat(header.length + 20)
  const rows = resources.map((r) => {
    const title = r.title ?? r.id
    return `${r.id.padEnd(idWidth)}  ${r.kind.padEnd(kindWidth)}  ${title}`
  })
  return [header, divider, ...rows].join('\n')
}

export function formatKnowledgeRolesList(roles: ReturnType<typeof listAllRoles>): string {
  if (roles.length === 0) return '(no results)'
  const idWidth = Math.max(...roles.map((r) => r.id.length), 4)
  const header = `${'ID'.padEnd(idWidth)}  TITLE`
  const divider = '-'.repeat(header.length + 20)
  const rows = roles.map((r) => `${r.id.padEnd(idWidth)}  ${r.title}`)
  return [header, divider, ...rows].join('\n')
}

export interface KnowledgeInvocationSource {
  kind: 'action' | 'agent-resource'
  id: string
  label: string
  targetNodeId: string
  invocations: OrganizationModelActionInvocation[]
}

export interface KnowledgeInvocationNeighborsResult {
  id: string
  title: string
  invocationSources: KnowledgeInvocationSource[]
}

export function normalizeKnowledgeNodeId(id: string): string {
  return id.startsWith('knowledge:') ? id.slice('knowledge:'.length) : id
}

export function formatKnowledgeInvocationLabel(invocation: OrganizationModelActionInvocation): string {
  switch (invocation.kind) {
    case 'slash-command':
      return invocation.command
    case 'mcp-tool':
      return `${invocation.server}/${invocation.name}`
    case 'api-endpoint':
      return `${invocation.method} ${invocation.path}`
    case 'script-execution':
      return invocation.resourceId
  }
}

function pushActionSource(
  sources: KnowledgeInvocationSource[],
  seen: Set<string>,
  action: OrganizationModelAction | undefined
): void {
  if (!action || action.invocations.length === 0) return

  const key = `action:${action.id}`
  if (seen.has(key)) return
  seen.add(key)

  sources.push({
    kind: 'action',
    id: action.id,
    label: action.label,
    targetNodeId: `action:${action.id}`,
    invocations: action.invocations
  })
}

function pushAgentResourceSource(
  sources: KnowledgeInvocationSource[],
  seen: Set<string>,
  resource: OrganizationModel['resources'][string] | undefined
): void {
  if (!resource || resource.kind !== 'agent' || resource.invocations.length === 0) return

  const agentResource = resource as OrganizationModelAgentResourceEntry
  const key = `agent-resource:${agentResource.id}`
  if (seen.has(key)) return
  seen.add(key)

  sources.push({
    kind: 'agent-resource',
    id: agentResource.id,
    label: agentResource.id,
    targetNodeId: `resource:${agentResource.id}`,
    invocations: agentResource.invocations
  })
}

export function resolveKnowledgeInvocationNeighbors(
  model: OrganizationModel,
  id: string
): KnowledgeInvocationNeighborsResult | undefined {
  const normalizedNodeId = normalizeKnowledgeNodeId(id)
  const node = model.knowledge[normalizedNodeId]
  if (!node) return undefined

  const sources: KnowledgeInvocationSource[] = []
  const seen = new Set<string>()

  for (const link of node.links) {
    const target = link.target

    if (target.kind === 'action') {
      pushActionSource(sources, seen, model.actions[target.id])
      continue
    }

    if (target.kind === 'resource') {
      pushAgentResourceSource(sources, seen, model.resources[target.id])
      continue
    }

    if (target.kind === 'system') {
      const system = getSystem(model, target.id)
      for (const actionRef of system?.actions ?? []) {
        pushActionSource(sources, seen, model.actions[actionRef.actionId])
      }

      for (const resource of Object.values(model.resources)) {
        if (resource.systemPath === target.id) {
          pushAgentResourceSource(sources, seen, resource)
        }
      }
    }
  }

  return {
    id: node.id,
    title: node.title,
    invocationSources: sources.sort((a, b) => a.targetNodeId.localeCompare(b.targetNodeId))
  }
}

export function formatKnowledgeInvocationNeighbors(result: KnowledgeInvocationNeighborsResult): string {
  const lines = [`Invocation-bearing graph neighbors for ${result.id}`, '']

  if (result.invocationSources.length === 0) {
    lines.push('  (none)')
  } else {
    for (const source of result.invocationSources) {
      lines.push(`${source.kind}: ${source.id}`)
      for (const invocation of source.invocations) {
        lines.push(`  ${invocation.kind}: ${formatKnowledgeInvocationLabel(invocation)}`)
      }
    }
  }

  return lines.join('\n')
}
