import type { Database, SqlValue } from 'sql.js';
import type { Tag, CreateTagInput, UpdateTagInput } from '../types/tag.js';

/**
 * TagRepository handles all database operations for tags.
 */
export class TagRepository {
  constructor(private db: Database) {}

  findAll(): Tag[] {
    const result = this.db.exec('SELECT * FROM tags ORDER BY name ASC');
    if (!result.length) return [];
    return this.rowsToTags(result[0]);
  }

  findById(id: number): Tag | null {
    const result = this.db.exec('SELECT * FROM tags WHERE id = ?', [id]);
    if (!result.length || !result[0].values.length) return null;
    const tags = this.rowsToTags(result[0]);
    return tags[0] || null;
  }

  findByName(name: string): Tag | null {
    const result = this.db.exec('SELECT * FROM tags WHERE name = ?', [name]);
    if (!result.length || !result[0].values.length) return null;
    const tags = this.rowsToTags(result[0]);
    return tags[0] || null;
  }

  findByRecipeId(recipeId: number): Tag[] {
    const sql = `SELECT t.* FROM tags t
    INNER JOIN recipe_tags rt ON rt.tag_id = t.id
    WHERE rt.recipe_id = ? ORDER BY t.name ASC`;
    const result = this.db.exec(sql, [recipeId]);
    if (!result.length) return [];
    return this.rowsToTags(result[0]);
  }

  create(input: CreateTagInput): Tag {
    const sql = 'INSERT INTO tags (name) VALUES (?)';
    this.db.run(sql, [input.name]);
    const result = this.db.exec('SELECT last_insert_rowid() as id');
    const id = result[0].values[0][0] as number;
    return this.findById(id)!;
  }

  update(id: number, input: UpdateTagInput): Tag | null {
    const existing = this.findById(id);
    if (!existing) return null;
    if (input.name === undefined) return existing;
    this.db.run('UPDATE tags SET name = ? WHERE id = ?', [input.name, id]);
    return this.findById(id)!;
  }

  delete(id: number): boolean {
    const existing = this.findById(id);
    if (!existing) return false;
    this.db.run('DELETE FROM tags WHERE id = ?', [id]);
    return true;
  }

  assignToRecipe(recipeId: number, tagId: number): boolean {
    try {
      this.db.run('INSERT INTO recipe_tags (recipe_id, tag_id) VALUES (?, ?)', [recipeId, tagId]);
      return true;
    } catch {
      return false;
    }
  }

  removeFromRecipe(recipeId: number, tagId: number): boolean {
    this.db.run('DELETE FROM recipe_tags WHERE recipe_id = ? AND tag_id = ?', [recipeId, tagId]);
    const result = this.db.exec('SELECT changes() as count');
    const count = result[0].values[0][0] as number;
    return count > 0;
  }

  private rowsToTags(result: { columns: string[]; values: SqlValue[][] }): Tag[] {
    return result.values.map((row) => {
      const tag: Record<string, SqlValue> = {};
      result.columns.forEach((col, idx) => { tag[col] = row[idx]; });
      return {
        id: tag.id as number,
        name: tag.name as string
      };
    });
  }
}
