import type { OrgKnowledgeNode } from '../organization-model/domains/knowledge'
import type {
  KnowledgeMount,
  OmDescribeDomainItem,
  OmDescribeKnowledge,
  OmDescribeOntology,
  OmDescribePolicy,
  OmDescribeResource,
  OmDescribeResult,
  OmDescribeRole,
  OmDescribeSystem,
  OmSearchHit,
  ParsedKnowledgePath
} from './queries'

// ---------------------------------------------------------------------------
// formatText
// ---------------------------------------------------------------------------

/**
 * Renders a list of `OrgKnowledgeNode` results as a human-friendly text table.
 *
 * Output format (one row per node):
 *   `<kind>  <id>  <title>  â€”  <summary (truncated to 80 chars)>`
 *
 * Returns `"(no results)"` when the array is empty.
 */
export function formatText(results: OrgKnowledgeNode[]): string {
  if (results.length === 0) {
    return '(no results)'
  }

  const kindWidth = Math.max(...results.map((n) => n.kind.length), 8)
  const idWidth = Math.max(...results.map((n) => n.id.length), 4)

  const header = `${'KIND'.padEnd(kindWidth)}  ${'ID'.padEnd(idWidth)}  TITLE`
  const divider = '-'.repeat(header.length + 20)

  const rows = results.map((n) => {
    const summary = n.summary.length > 80 ? n.summary.slice(0, 77) + '...' : n.summary
    return `${n.kind.padEnd(kindWidth)}  ${n.id.padEnd(idWidth)}  ${n.title}  â€”  ${summary}`
  })

  return [header, divider, ...rows].join('\n')
}

// ---------------------------------------------------------------------------
// formatJson
// ---------------------------------------------------------------------------

/**
 * Wrapped JSON envelope for machine-readable output.
 *
 * Shape: `{ path, mount, args, results }`
 *
 * - `path`    â€” the original path string passed to `parsePath`
 * - `mount`   â€” the resolved mount axis
 * - `args`    â€” the parsed arguments array
 * - `results` â€” the query results (array of `OrgKnowledgeNode` or string IDs)
 */
export interface KnowledgeJsonEnvelope {
  path: string
  mount: KnowledgeMount
  args: string[]
  results: OrgKnowledgeNode[] | string[]
}

/**
 * Formats query results as a wrapped JSON envelope string.
 *
 * The envelope shape is `{ path, mount, args, results }`.  This is intentionally
 * NOT flat `{ results }` â€” consumers (agent skills, jq pipelines) need the
 * mount + args to know how to interpret the results array.
 *
 * @param input.path    - The original path string (e.g. `"/by-system/sales.crm"`).
 * @param input.mount   - The resolved mount axis.
 * @param input.args    - The parsed argument array.
 * @param input.results - The query results.
 */
export function formatJson(input: {
  path: string
  parsed: ParsedKnowledgePath
  results: OrgKnowledgeNode[] | string[]
}): string {
  const envelope: KnowledgeJsonEnvelope = {
    path: input.path,
    mount: input.parsed.mount,
    args: input.parsed.args,
    results: input.results
  }
  return JSON.stringify(envelope, null, 2)
}

// ---------------------------------------------------------------------------
// formatIdsOnly
// ---------------------------------------------------------------------------

/**
 * Renders results as newline-separated IDs for piping.
 *
 * - For `OrgKnowledgeNode[]` results: emits `node.id` per line.
 * - For `string[]` results (governs / governedBy): emits each string per line.
 *
 * Returns an empty string when the array is empty (suitable for `wc -l` piping).
 */
export function formatIdsOnly(results: OrgKnowledgeNode[] | string[] | OmSearchHit[]): string {
  if (results.length === 0) return ''

  const ids = results.map((r) => (typeof r === 'string' ? r : r.id))
  return ids.join('\n')
}

// ---------------------------------------------------------------------------
// formatOmSearchHits
// ---------------------------------------------------------------------------

/**
 * Renders `omSearch()` results as a human-readable table.
 *
 * Format (one row per hit):
 *   `[<kind>]  <id>  —  <title>  —  <summary (truncated to 80 chars)>`
 *
 * Hits are pre-sorted by score (descending) by the caller. Returns
 * `"(no results)"` when the array is empty.
 */
export function formatOmSearchHits(hits: OmSearchHit[]): string {
  if (hits.length === 0) return '(no results)'

  const kindWidth = Math.max(...hits.map((h) => labelForKind(h).length), 6)
  const idWidth = Math.max(...hits.map((h) => h.id.length), 4)

  const rows = hits.map((hit) => {
    const summary = hit.summary.length > 80 ? hit.summary.slice(0, 77) + '...' : hit.summary
    const kindLabel = labelForKind(hit).padEnd(kindWidth)
    const idLabel = hit.id.padEnd(idWidth)
    const tail = summary ? `  —  ${hit.title}  —  ${summary}` : `  —  ${hit.title}`
    return `${kindLabel}  ${idLabel}${tail}`
  })

  return rows.join('\n')
}

function labelForKind(hit: OmSearchHit): string {
  return hit.subKind ? `[${hit.kind}/${hit.subKind}]` : `[${hit.kind}]`
}

// ---------------------------------------------------------------------------
// formatOmDescribe
// ---------------------------------------------------------------------------

/**
 * Renders an `OmDescribeResult` neighborhood view as multi-section text.
 *
 * Each kind gets a tailored layout (system shows resources + governing
 * knowledge; knowledge shows body excerpt + governs; etc.). Returns
 * `"(node not found)"` when the result is `undefined`.
 */
export function formatOmDescribe(result: OmDescribeResult | undefined): string {
  if (!result) return '(node not found)'

  switch (result.kind) {
    case 'system':
      return formatSystemDescribe(result)
    case 'resource':
      return formatResourceDescribe(result)
    case 'knowledge':
      return formatKnowledgeDescribe(result)
    case 'ontology':
      return formatOntologyDescribe(result)
    case 'role':
      return formatRoleDescribe(result)
    case 'policy':
      return formatPolicyDescribe(result)
    case 'domain-item':
      return formatDomainItemDescribe(result)
  }
}

function section(title: string, body: string): string {
  return body ? `${title}:\n${body}` : ''
}

function bullet(items: string[]): string {
  if (items.length === 0) return '  (none)'
  return items.map((i) => `  - ${i}`).join('\n')
}

function field(label: string, value: string | undefined | null): string {
  return value === undefined || value === null || value === '' ? '' : `${label}: ${value}`
}

function join(parts: string[]): string {
  return parts.filter((p) => p.length > 0).join('\n\n')
}

function formatSystemDescribe(r: OmDescribeSystem): string {
  const header = join([
    `System: ${r.id}`,
    [
      field('Label', r.label),
      field('Kind', r.systemKind),
      field('Lifecycle', r.lifecycle),
      field('Parent', r.parentSystemId),
      field('Responsible role', r.responsibleRoleId)
    ]
      .filter((s) => s.length > 0)
      .join('\n'),
    r.description ? `Description: ${r.description}` : ''
  ])

  const govSection = section('Governed by knowledge', bullet(r.governingKnowledgeIds))

  const resourceLines: string[] = []
  const totalResources = Object.values(r.resourceCountsByKind).reduce((a, b) => a + b, 0)
  if (totalResources > 0) {
    for (const [kind, count] of Object.entries(r.resourceCountsByKind)) {
      const ids = r.resourceIdsByKind[kind] ?? []
      const preview = ids.slice(0, 5).join(', ')
      const tail = ids.length > 5 ? `, ... (+${ids.length - 5})` : ''
      resourceLines.push(`  ${kind}s (${count}): ${preview}${tail}`)
    }
  } else {
    resourceLines.push('  (none)')
  }
  const resourceSection = `Resources (${totalResources}):\n${resourceLines.join('\n')}`

  const ontologyEntries = Object.entries(r.ontologyCountsByKind)
  const ontologySection = ontologyEntries.length
    ? `Ontology: ${ontologyEntries.map(([k, n]) => `${k}s: ${n}`).join(', ')}`
    : ''

  const childSection = section('Subsystems', bullet(r.childSystemPaths))

  return join([header, govSection, resourceSection, ontologySection, childSection])
}

function formatResourceDescribe(r: OmDescribeResource): string {
  const header = join([
    `Resource: ${r.id}`,
    [
      field('Kind', r.resourceKind),
      field('System', r.systemPath),
      field('Title', r.title),
      field('Status', r.status),
      field('Owner role', r.ownerRoleId)
    ]
      .filter((s) => s.length > 0)
      .join('\n'),
    r.description ? `Description: ${r.description}` : ''
  ])

  const ontologyLines: string[] = []
  if (r.ontology) {
    if (r.ontology.primaryAction) ontologyLines.push(`  primary action: ${r.ontology.primaryAction}`)
    if (r.ontology.actions?.length) ontologyLines.push(`  actions: ${r.ontology.actions.join(', ')}`)
    if (r.ontology.reads?.length) ontologyLines.push(`  reads: ${r.ontology.reads.join(', ')}`)
    if (r.ontology.writes?.length) ontologyLines.push(`  writes: ${r.ontology.writes.join(', ')}`)
    if (r.ontology.emits?.length) ontologyLines.push(`  emits: ${r.ontology.emits.join(', ')}`)
    if (r.ontology.usesCatalogs?.length) ontologyLines.push(`  uses catalogs: ${r.ontology.usesCatalogs.join(', ')}`)
  }
  const ontologySection = ontologyLines.length ? `Ontology bindings:\n${ontologyLines.join('\n')}` : ''

  const codeRefsSection = r.codeRefPaths.length ? `Code refs:\n${bullet(r.codeRefPaths)}` : ''

  return join([header, ontologySection, codeRefsSection])
}

function formatKnowledgeDescribe(r: OmDescribeKnowledge): string {
  const header = join([
    `Knowledge: ${r.id}`,
    [field('Kind', r.knowledgeKind), field('Title', r.title), field('Updated', r.updatedAt)]
      .filter((s) => s.length > 0)
      .join('\n'),
    r.summary ? `Summary: ${r.summary}` : ''
  ])

  const ownerSection = r.ownerIds.length ? `Owned by:\n${bullet(r.ownerIds)}` : ''
  const govSection = section('Governs', bullet(r.governs))
  const bodySection = `Body excerpt (${r.bodyLineCount} lines total — use knowledge:cat for full):\n${r.bodyExcerpt}`

  return join([header, ownerSection, govSection, bodySection])
}

function formatOntologyDescribe(r: OmDescribeOntology): string {
  const header = join([
    `Ontology: ${r.id}`,
    [field('Kind', r.ontologyKind), field('Scope', r.scope), field('Local id', r.localId), field('Label', r.label)]
      .filter((s) => s.length > 0)
      .join('\n'),
    r.description ? `Description: ${r.description}` : ''
  ])

  const govSection = section('Governed by knowledge', bullet(r.governingKnowledgeIds))
  return join([header, govSection])
}

function formatRoleDescribe(r: OmDescribeRole): string {
  const header = join([
    `Role: ${r.id}`,
    [field('Title', r.title), field('Reports to', r.reportsToId)].filter((s) => s.length > 0).join('\n')
  ])

  const respSection = section('Responsibilities', bullet(r.responsibilities))
  const responsibleForSection = section('Responsible for systems', bullet(r.responsibleFor))

  return join([header, respSection, responsibleForSection])
}

function formatPolicyDescribe(r: OmDescribePolicy): string {
  const header = join([
    `Policy: ${r.id}`,
    [field('Label', r.label), field('Trigger', r.triggerKind)].filter((s) => s.length > 0).join('\n'),
    r.description ? `Description: ${r.description}` : ''
  ])

  const effectSection = section('Effect kinds', bullet(r.effectKinds))
  const appliesLines: string[] = []
  if (r.appliesToSystemIds.length) appliesLines.push(`  systems: ${r.appliesToSystemIds.join(', ')}`)
  if (r.appliesToActionIds.length) appliesLines.push(`  actions: ${r.appliesToActionIds.join(', ')}`)
  if (r.appliesToResourceIds.length) appliesLines.push(`  resources: ${r.appliesToResourceIds.join(', ')}`)
  if (r.appliesToRoleIds.length) appliesLines.push(`  roles: ${r.appliesToRoleIds.join(', ')}`)
  const appliesSection = appliesLines.length ? `Applies to:\n${appliesLines.join('\n')}` : ''

  return join([header, effectSection, appliesSection])
}

function formatDomainItemDescribe(r: OmDescribeDomainItem): string {
  const header = join([
    `Domain Item: ${r.id}`,
    [
      field('Domain', r.location.domainLabel),
      field('Name', r.name),
      field('Graph node', r.location.graphNodeId),
      field('Parent nodes', r.location.containsParentIds.join(', '))
    ]
      .filter((s) => s.length > 0)
      .join('\n')
  ])

  // Key record fields — emit a compact subset of the record payload.
  const recordLines: string[] = []
  for (const [k, v] of Object.entries(r.record)) {
    if (v === undefined || v === null) continue
    if (typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0) continue
    const display = Array.isArray(v)
      ? v.length === 0
        ? null
        : `[${(v as unknown[]).slice(0, 5).join(', ')}${v.length > 5 ? `, ...+${v.length - 5}` : ''}]`
      : typeof v === 'object'
        ? JSON.stringify(v).slice(0, 120)
        : String(v)
    if (display === null) continue
    recordLines.push(`  ${k}: ${display}`)
  }
  const recordSection = recordLines.length ? `Record:\n${recordLines.join('\n')}` : ''

  const govLines = r.governingKnowledge.map((g) => `  - ${g.id} [${g.knowledgeKind}] ${g.title}`)
  const govSection = govLines.length ? `Governing knowledge:\n${govLines.join('\n')}` : 'Governing knowledge:\n  (none)'

  return join([header, recordSection, govSection])
}
