/*
 * FeedItemStore.vala
 * 
 * CRUD operations for feed items with FTS search support.
 */

/**
 * FeedItemStore - Manages feed item persistence
 */
public class RSSuper.FeedItemStore : Object {
    private Database db;
    
    /**
     * Signal emitted when an item is added
     */
    public signal void item_added(FeedItem item);
    
    /**
     * Signal emitted when an item is updated
     */
    public signal void item_updated(FeedItem item);
    
    /**
     * Signal emitted when an item is deleted
     */
    public signal void item_deleted(string id);
    
    /**
     * Create a new feed item store
     */
    public FeedItemStore(Database db) {
        this.db = db;
    }
    
    /**
     * Add a new feed item
     */
    public FeedItem add(FeedItem item) throws Error {
        var stmt = db.prepare(
            "INSERT INTO feed_items (id, subscription_id, title, link, description, content, " +
            "author, published, updated, categories, enclosure_url, enclosure_type, " +
            "enclosure_length, guid, is_read, is_starred) " +
            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"
        );
        
        stmt.bind_text(1, item.id, -1, null);
        stmt.bind_text(2, item.subscription_title ?? "", -1, null);
        stmt.bind_text(3, item.title, -1, null);
        stmt.bind_text(4, item.link ?? "", -1, null);
        stmt.bind_text(5, item.description ?? "", -1, null);
        stmt.bind_text(6, item.content ?? "", -1, null);
        stmt.bind_text(7, item.author ?? "", -1, null);
        stmt.bind_text(8, item.published ?? "", -1, null);
        stmt.bind_text(9, item.updated ?? "", -1, null);
        stmt.bind_text(10, format_categories(item.categories), -1, null);
        stmt.bind_text(11, item.enclosure_url ?? "", -1, null);
        stmt.bind_text(12, item.enclosure_type ?? "", -1, null);
        stmt.bind_text(13, item.enclosure_length ?? "", -1, null);
        stmt.bind_text(14, item.guid ?? "", -1, null);
        stmt.bind_int(15, 0);  // is_read
        stmt.bind_int(16, 0);  // is_starred
        
        stmt.step();
        
        debug("Feed item added: %s", item.id);
        item_added(item);
        
        return item;
    }
    
    /**
     * Add multiple items in a batch
     */
    public void add_batch(FeedItem[] items) throws Error {
        db.begin_transaction();
        try {
            foreach (var item in items) {
                add(item);
            }
            db.commit();
            debug("Batch insert completed: %d items", items.length);
        } catch (Error e) {
            db.rollback();
            throw new DBError.FAILED("Transaction failed: %s".printf(e.message));
        }
    }
    
    /**
     * Get an item by ID
     */
    public FeedItem? get_by_id(string id) throws Error {
        var stmt = db.prepare(
            "SELECT id, subscription_id, title, link, description, content, author, " +
            "published, updated, categories, enclosure_url, enclosure_type, " +
            "enclosure_length, guid, is_read, is_starred " +
            "FROM feed_items WHERE id = ?;"
        );
        
        stmt.bind_text(1, id, -1, null);
        
        if (stmt.step() == Sqlite.ROW) {
            return row_to_item(stmt);
        }
        
        return null;
    }
    
    /**
     * Get items by subscription ID
     */
    public FeedItem[] get_by_subscription(string subscription_id) throws Error {
        var items = new GLib.List<FeedItem?>();
        
        var stmt = db.prepare(
            "SELECT id, subscription_id, title, link, description, content, author, " +
            "published, updated, categories, enclosure_url, enclosure_type, " +
            "enclosure_length, guid, is_read, is_starred " +
            "FROM feed_items WHERE subscription_id = ? " +
            "ORDER BY published DESC LIMIT 100;"
        );
        
        stmt.bind_text(1, subscription_id, -1, null);
        
        while (stmt.step() == Sqlite.ROW) {
            var item = row_to_item(stmt);
            if (item != null) {
                items.append(item);
            }
        }
        
        return items_to_array(items);
    }
    
    /**
     * Get all items
     */
    public FeedItem[] get_all() throws Error {
        var items = new GLib.List<FeedItem?>();
        
        var stmt = db.prepare(
            "SELECT id, subscription_id, title, link, description, content, author, " +
            "published, updated, categories, enclosure_url, enclosure_type, " +
            "enclosure_length, guid, is_read, is_starred " +
            "FROM feed_items ORDER BY published DESC LIMIT 1000;"
        );
        
        while (stmt.step() == Sqlite.ROW) {
            var item = row_to_item(stmt);
            if (item != null) {
                items.append(item);
            }
        }
        
        return items_to_array(items);
    }
    
    /**
     * Search items using FTS
     */
    public FeedItem[] search(string query, int limit = 50) throws Error {
        var items = new GLib.List<FeedItem?>();
        
        var stmt = db.prepare(
            "SELECT f.id, f.subscription_id, f.title, f.link, f.description, f.content, " +
            "f.author, f.published, f.updated, f.categories, f.enclosure_url, " +
            "f.enclosure_type, f.enclosure_length, f.guid, f.is_read, f.is_starred " +
            "FROM feed_items_fts t " +
            "JOIN feed_items f ON t.rowid = f.rowid " +
            "WHERE feed_items_fts MATCH ? " +
            "ORDER BY rank " +
            "LIMIT ?;"
        );
        
        stmt.bind_text(1, query, -1, null);
        stmt.bind_int(2, limit);
        
        while (stmt.step() == Sqlite.ROW) {
            var item = row_to_item(stmt);
            if (item != null) {
                items.append(item);
            }
        }
        
        return items_to_array(items);
    }
    
    /**
     * Mark an item as read
     */
    public void mark_as_read(string id) throws Error {
        var stmt = db.prepare("UPDATE feed_items SET is_read = 1 WHERE id = ?;");
        stmt.bind_text(1, id, -1, null);
        stmt.step();
        
        debug("Item marked as read: %s", id);
    }
    
    /**
     * Mark an item as unread
     */
    public void mark_as_unread(string id) throws Error {
        var stmt = db.prepare("UPDATE feed_items SET is_read = 0 WHERE id = ?;");
        stmt.bind_text(1, id, -1, null);
        stmt.step();
        
        debug("Item marked as unread: %s", id);
    }
    
    /**
     * Mark an item as starred
     */
    public void mark_as_starred(string id) throws Error {
        var stmt = db.prepare("UPDATE feed_items SET is_starred = 1 WHERE id = ?;");
        stmt.bind_text(1, id, -1, null);
        stmt.step();
        
        debug("Item starred: %s", id);
    }
    
    /**
     * Unmark an item from starred
     */
    public void unmark_starred(string id) throws Error {
        var stmt = db.prepare("UPDATE feed_items SET is_starred = 0 WHERE id = ?;");
        stmt.bind_text(1, id, -1, null);
        stmt.step();
        
        debug("Item unstarred: %s", id);
    }
    
    /**
     * Get unread items
     */
    public FeedItem[] get_unread() throws Error {
        var items = new GLib.List<FeedItem?>();
        
        var stmt = db.prepare(
            "SELECT id, subscription_id, title, link, description, content, author, " +
            "published, updated, categories, enclosure_url, enclosure_type, " +
            "enclosure_length, guid, is_read, is_starred " +
            "FROM feed_items WHERE is_read = 0 " +
            "ORDER BY published DESC LIMIT 100;"
        );
        
        while (stmt.step() == Sqlite.ROW) {
            var item = row_to_item(stmt);
            if (item != null) {
                items.append(item);
            }
        }
        
        return items_to_array(items);
    }
    
    /**
     * Get starred items
     */
    public FeedItem[] get_starred() throws Error {
        var items = new GLib.List<FeedItem?>();
        
        var stmt = db.prepare(
            "SELECT id, subscription_id, title, link, description, content, author, " +
            "published, updated, categories, enclosure_url, enclosure_type, " +
            "enclosure_length, guid, is_read, is_starred " +
            "FROM feed_items WHERE is_starred = 1 " +
            "ORDER BY published DESC LIMIT 100;"
        );
        
        while (stmt.step() == Sqlite.ROW) {
            var item = row_to_item(stmt);
            if (item != null) {
                items.append(item);
            }
        }
        
        return items_to_array(items);
    }
    
    /**
     * Delete an item by ID
     */
    public void delete(string id) throws Error {
        var stmt = db.prepare("DELETE FROM feed_items WHERE id = ?;");
        stmt.bind_text(1, id, -1, null);
        stmt.step();
        
        debug("Item deleted: %s", id);
        item_deleted(id);
    }
    
    /**
     * Delete items by subscription ID
     */
    public void delete_by_subscription(string subscription_id) throws Error {
        var stmt = db.prepare("DELETE FROM feed_items WHERE subscription_id = ?;");
        stmt.bind_text(1, subscription_id, -1, null);
        stmt.step();
        
        debug("Items deleted for subscription: %s", subscription_id);
    }
    
    /**
     * Delete old items (keep last N items per subscription)
     */
    public void cleanup_old_items(int keep_count = 100) throws Error {
        db.begin_transaction();
        try {
            var stmt = db.prepare(
                "DELETE FROM feed_items WHERE id NOT IN (" +
                "SELECT id FROM feed_items " +
                "ORDER BY published DESC " +
                "LIMIT -1 OFFSET ?" +
                ");"
            );
            stmt.bind_int(1, keep_count);
            stmt.step();
            
            db.commit();
            debug("Old items cleaned up, kept %d", keep_count);
        } catch (Error e) {
            db.rollback();
            throw new DBError.FAILED("Transaction failed: %s".printf(e.message));
        }
    }
    
    /**
     * Convert a database row to a FeedItem
     */
    private FeedItem? row_to_item(Sqlite.Statement stmt) {
        try {
            string categories_str = stmt.column_text(9);
            string[] categories = parse_categories(categories_str);
            
            var item = new FeedItem.with_values(
                stmt.column_text(0),  // id
                stmt.column_text(2),  // title
                stmt.column_text(3),  // link
                stmt.column_text(4),  // description
                stmt.column_text(5),  // content
                stmt.column_text(6),  // author
                stmt.column_text(7),  // published
                stmt.column_text(8),  // updated
                categories,
                stmt.column_text(10), // enclosure_url
                stmt.column_text(11), // enclosure_type
                stmt.column_text(12), // enclosure_length
                stmt.column_text(13), // guid
                stmt.column_text(1)   // subscription_id (stored as subscription_title)
            );
            
            return item;
        } catch (Error e) {
            warning("Failed to parse item row: %s", e.message);
            return null;
        }
    }
    
    /**
     * Format categories array as JSON string
     */
    private string format_categories(string[] categories) {
        if (categories.length == 0) {
            return "[]";
        }
        
        var sb = new StringBuilder();
        sb.append("[");
        for (var i = 0; i < categories.length; i++) {
            if (i > 0) sb.append(",");
            sb.append("\"");
            sb.append(categories[i]);
            sb.append("\"");
        }
        sb.append("]");
        
        return sb.str;
    }
    
    /**
     * Parse categories from JSON string
     */
    private string[] parse_categories(string json) {
        if (json == null || json.length == 0 || json == "[]") {
            return {};
        }
        
        try {
            var parser = new Json.Parser();
            if (parser.load_from_data(json)) {
                var node = parser.get_root();
                if (node.get_node_type() == Json.NodeType.ARRAY) {
                    var array = node.get_array();
                    var categories = new string[array.get_length()];
                    for (var i = 0; i < array.get_length(); i++) {
                        categories[i] = array.get_string_element(i);
                    }
                    return categories;
                }
            }
        } catch (Error e) {
            warning("Failed to parse categories: %s", e.message);
        }
        
        return {};
    }

    private FeedItem[] items_to_array(GLib.List<FeedItem?> list) {
        FeedItem[] arr = {};
        for (unowned var node = list; node != null; node = node.next) {
            if (node.data != null) arr += node.data;
        }
        return arr;
    }

}

