import { Injectable } from '@nestjs/common';

type ProseMirrorNode = {
  type?: string;
  text?: string;
  attrs?: Record<string, any>;
  content?: ProseMirrorNode[];
};

@Injectable()
export class ProseMirrorTextService {
  toText(serialized: string): string {
    if (!serialized) {
      return '';
    }

    let doc: ProseMirrorNode;
    try {
      doc = JSON.parse(serialized);
    } catch {
      return serialized;
    }

    const lines: string[] = [];
    this.renderNode(doc, lines, 0, { listIndex: [] });
    return lines.join('\n').replace(/\n{3,}/g, '\n\n').trim();
  }

  private renderNode(
    node: ProseMirrorNode,
    lines: string[],
    indentLevel: number,
    context: { listIndex: number[] },
  ) {
    if (!node) return;

    switch (node.type) {
      case 'doc':
        node.content?.forEach((child) => this.renderNode(child, lines, indentLevel, context));
        return;
      case 'heading': {
        const level = Math.min(Math.max(node.attrs?.level ?? 1, 1), 6);
        const text = this.renderInline(node);
        lines.push(`${'#'.repeat(level)} ${text}`.trim());
        lines.push('');
        return;
      }
      case 'paragraph': {
        const text = this.renderInline(node);
        if (text.trim() !== '') {
          lines.push(this.indent(text, indentLevel));
          lines.push('');
        }
        return;
      }
      case 'bulletList': {
        node.content?.forEach((child) => this.renderNode(child, lines, indentLevel, context));
        lines.push('');
        return;
      }
      case 'orderedList': {
        context.listIndex.push(0);
        node.content?.forEach((child) => this.renderNode(child, lines, indentLevel, context));
        context.listIndex.pop();
        lines.push('');
        return;
      }
      case 'listItem': {
        const isOrdered = context.listIndex.length > 0;
        if (isOrdered) {
          const index = context.listIndex.length - 1;
          context.listIndex[index] += 1;
        }
        const prefix = isOrdered
          ? `${context.listIndex[context.listIndex.length - 1]}. `
          : '- ';
        const buffer: string[] = [];
        node.content?.forEach((child) => this.renderNode(child, buffer, indentLevel + 1, context));
        const itemText = buffer.join('\n').trim();
        if (itemText) {
          const line = this.indent(`${prefix}${itemText}`, indentLevel);
          lines.push(line);
        }
        return;
      }
      case 'blockquote': {
        const text = this.renderInline(node);
        if (text) {
          lines.push(this.indent(`> ${text}`, indentLevel));
          lines.push('');
        }
        return;
      }
      case 'codeBlock': {
        const text = this.renderInline(node);
        lines.push(this.indent('```', indentLevel));
        if (text) {
          lines.push(this.indent(text, indentLevel));
        }
        lines.push(this.indent('```', indentLevel));
        lines.push('');
        return;
      }
      case 'hardBreak':
        lines.push('');
        return;
      case 'horizontalRule':
        lines.push(this.indent('---', indentLevel));
        lines.push('');
        return;
      case 'table': {
        const rows: string[] = [];
        node.content?.forEach((row) => {
          const cells: string[] = [];
          row.content?.forEach((cell) => {
            cells.push(this.renderInline(cell));
          });
          rows.push(`| ${cells.join(' | ')} |`);
        });
        rows.forEach((row) => lines.push(this.indent(row, indentLevel)));
        lines.push('');
        return;
      }
      default:
        if (node.content) {
          node.content.forEach((child) => this.renderNode(child, lines, indentLevel, context));
        } else if (node.text) {
          lines.push(this.indent(node.text, indentLevel));
        }
    }
  }

  private renderInline(node: ProseMirrorNode): string {
    if (node.type === 'text') {
      return node.text ?? '';
    }
    const parts: string[] = [];
    node.content?.forEach((child) => {
      if (child.type === 'hardBreak') {
        parts.push('\n');
      } else {
        parts.push(this.renderInline(child));
      }
    });
    return parts.join('');
  }

  private indent(text: string, level: number): string {
    if (level <= 0) return text;
    return `${'  '.repeat(level)}${text}`;
  }
}
