import { db } from "./db";
import { words, examples, tags, wordTags, seeAlso } from "./db/schema";
import { eq } from "drizzle-orm";
import type { Word, Tag, NewWord, TagLevel } from "@/types/types";

class PGStorage {
  async getAllWords(): Promise<Word[]> {
    const allWords = await db.query.words.findMany({
      with: {
        examples: true,
        tags: {
          with: {
            tag: true,
          },
        },
        seeAlso: true,
      },
    });

    return allWords.map((word) => ({
      id: word.id,
      hindi: word.hindi,
      english: word.english,
      note: word.note,
      examples: word.examples.length > 0 ? word.examples : undefined,
      tags: word.tags.map((wt) => ({
        id: wt.tag.id,
        name: wt.tag.name,
        description: wt.tag.description,
        level: wt.tag.level as TagLevel,
        parentPageId: wt.tag.parentPageId,
      })),
      seeAlso: word.seeAlso.length > 0 ? word.seeAlso : undefined,
    }));
  }

  async getWordById(id: number): Promise<Word | null> {
    const word = await db.query.words.findFirst({
      where: eq(words.id, id),
      with: {
        examples: true,
        tags: {
          with: {
            tag: true,
          },
        },
        seeAlso: true,
      },
    });

    if (!word) return null;

    return {
      id: word.id,
      hindi: word.hindi,
      english: word.english,
      note: word.note,
      examples: word.examples.length > 0 ? word.examples : undefined,
      tags: word.tags.map((wt) => ({
        id: wt.tag.id,
        name: wt.tag.name,
        description: wt.tag.description,
        level: wt.tag.level as TagLevel,
        parentPageId: wt.tag.parentPageId,
      })),
      seeAlso: word.seeAlso.length > 0 ? word.seeAlso : undefined,
    };
  }

  async getWordsByTag(tagName: string): Promise<Word[]> {
    const tag = await db.query.tags.findFirst({
      where: eq(tags.name, tagName),
      with: {
        words: {
          with: {
            word: {
              with: {
                examples: true,
                tags: {
                  with: {
                    tag: true,
                  },
                },
                seeAlso: true,
              },
            },
          },
        },
      },
    });

    if (!tag) return [];

    return tag.words.map((wt) => ({
      id: wt.word.id,
      hindi: wt.word.hindi,
      english: wt.word.english,
      note: wt.word.note,
      examples: wt.word.examples.length > 0 ? wt.word.examples : undefined,
      tags: wt.word.tags.map((t) => ({
        id: t.tag.id,
        name: t.tag.name,
        description: t.tag.description,
        level: t.tag.level as TagLevel,
        parentPageId: t.tag.parentPageId,
      })),
      seeAlso: wt.word.seeAlso.length > 0 ? wt.word.seeAlso.map(sa => ({
        ...sa,
        reference: sa.reference || '',
      })) : undefined,
    }));
  }

  async getAllTags(): Promise<Tag[]> {
    const dbTags = await db.query.tags.findMany();
    return dbTags.map(tag => ({
      id: tag.id,
      name: tag.name,
      description: tag.description,
      level: tag.level as TagLevel,
      parentPageId: tag.parentPageId,
    }));
  }

  async getPageTags(): Promise<Tag[]> {
    const dbTags = await db.query.tags.findMany({
      where: eq(tags.level, 'page'),
    });
    return dbTags.map(tag => ({
      id: tag.id,
      name: tag.name,
      description: tag.description,
      level: tag.level as TagLevel,
      parentPageId: tag.parentPageId,
    }));
  }

  async getGrammarTags(): Promise<Tag[]> {
    const dbTags = await db.query.tags.findMany({
      where: eq(tags.level, 'grammar'),
    });
    return dbTags.map(tag => ({
      id: tag.id,
      name: tag.name,
      description: tag.description,
      level: tag.level as TagLevel,
      parentPageId: tag.parentPageId,
    }));
  }

  async getGenderTags(): Promise<Tag[]> {
    const dbTags = await db.query.tags.findMany({
      where: eq(tags.level, 'gender'),
    });
    return dbTags.map(tag => ({
      id: tag.id,
      name: tag.name,
      description: tag.description,
      level: tag.level as TagLevel,
      parentPageId: tag.parentPageId,
    }));
  }

  async getAllTypes(): Promise<string[]> {
    const grammarTags = await this.getGrammarTags();
    return grammarTags.map(t => t.name).sort();
  }

  async createWord(newWord: NewWord): Promise<Word> {
    const [word] = await db
      .insert(words)
      .values({
        hindi: newWord.hindi,
        english: newWord.english,
        note: newWord.note || null,
      })
      .returning();

    // Add examples
    if (newWord.examples) {
      for (const example of newWord.examples) {
        await db.insert(examples).values({
          wordId: word.id,
          hindi: example.hindi,
          english: example.english,
          note: example.note || null,
        });
      }
    }

    // Add tags
    if (newWord.tagIds) {
      for (const tagId of newWord.tagIds) {
        await db.insert(wordTags).values({
          wordId: word.id,
          tagId: tagId,
        });
      }
    }

    // Add see also references
    if (newWord.seeAlso) {
      for (const ref of newWord.seeAlso) {
        await db.insert(seeAlso).values({
          wordId: word.id,
          reference: ref.reference,
          note: ref.note || null,
        });
      }
    }

    // Fetch the complete word with all relations
    const createdWord = await this.getWordById(word.id);
    return createdWord!;
  }

  async createTag(name: string, level: TagLevel, description?: string, parentPageId?: number): Promise<Tag> {
    const [tag] = await db
      .insert(tags)
      .values({
        name,
        description: description || null,
        level,
        parentPageId: parentPageId || undefined,
      })
      .returning();

    return {
      id: tag.id,
      name: tag.name,
      description: tag.description,
      level: tag.level as TagLevel,
      parentPageId: tag.parentPageId,
    };
  }

  async deleteWord(id: number): Promise<void> {
    await db.delete(words).where(eq(words.id, id));
  }
}

export const storage = new PGStorage();
