#include "core/parse_md_frontmatter.h"

#include <algorithm>
#include <sstream>

namespace fn_md {

namespace {

// ---------------------------------------------------------------------------
// String helpers
// ---------------------------------------------------------------------------

static std::string trim(const std::string& s) {
    const auto b = s.find_first_not_of(" \t\r");
    if (b == std::string::npos) return {};
    const auto e = s.find_last_not_of(" \t\r");
    return s.substr(b, e - b + 1);
}

// Strip a trailing `  # comment` from a value string.
// Only strips if the `#` is preceded by at least one space and is outside
// any quote context.  We take a simple approach: after stripping outer quotes
// we look for ` #` outside of them.
static std::string strip_comment(const std::string& s) {
    // If the whole string is quoted, skip comment stripping; the comment would
    // be inside the quotes and should be preserved as literal text.
    if (s.size() >= 2 &&
        ((s.front() == '"' && s.back() == '"') ||
         (s.front() == '\'' && s.back() == '\''))) {
        return s;
    }
    const auto pos = s.find(" #");
    if (pos == std::string::npos) return s;
    return trim(s.substr(0, pos));
}

// Remove surrounding " or ' quotes from a value, if present.
static std::string unquote(const std::string& s) {
    if (s.size() >= 2 &&
        ((s.front() == '"' && s.back() == '"') ||
         (s.front() == '\'' && s.back() == '\''))) {
        return s.substr(1, s.size() - 2);
    }
    return s;
}

// Parse an inline YAML list:  [a, b, c]  or  []
static std::vector<std::string> parse_inline_list(const std::string& s) {
    std::vector<std::string> result;
    // strip brackets
    const auto lb = s.find('[');
    const auto rb = s.rfind(']');
    if (lb == std::string::npos || rb == std::string::npos || lb >= rb)
        return result;
    const std::string inner = s.substr(lb + 1, rb - lb - 1);
    if (trim(inner).empty()) return result;

    // split on commas respecting quotes
    std::string token;
    bool in_quote = false;
    char quote_ch = 0;
    for (char c : inner) {
        if (!in_quote && (c == '"' || c == '\'')) {
            in_quote = true;
            quote_ch = c;
            token += c;
        } else if (in_quote && c == quote_ch) {
            in_quote = false;
            token += c;
        } else if (!in_quote && c == ',') {
            result.push_back(unquote(trim(token)));
            token.clear();
        } else {
            token += c;
        }
    }
    const auto t = unquote(trim(token));
    if (!t.empty()) result.push_back(t);
    return result;
}

// Split a frontmatter line into (key, raw_value).
// Returns false if the line has no `:`.
static bool split_kv(const std::string& line, std::string& key, std::string& raw_val) {
    const auto colon = line.find(':');
    if (colon == std::string::npos) return false;
    key     = trim(line.substr(0, colon));
    raw_val = trim(line.substr(colon + 1));
    return true;
}

// ---------------------------------------------------------------------------
// Line iterator helpers
// ---------------------------------------------------------------------------

static std::vector<std::string> split_lines(const std::string& s) {
    std::vector<std::string> lines;
    std::istringstream ss(s);
    std::string l;
    while (std::getline(ss, l)) lines.push_back(l);
    return lines;
}

// Returns true if a line looks like a block-list item: optional leading
// whitespace, then `- `.
static bool is_list_item(const std::string& line, std::string& item_val) {
    const auto b = line.find_first_not_of(" \t");
    if (b == std::string::npos) return false;
    if (line[b] != '-') return false;
    // Must have space (or be end) after `-`
    if (b + 1 < line.size() && line[b + 1] != ' ') return false;
    item_val = trim(line.substr(b + 1));
    item_val = unquote(item_val);
    return true;
}

} // anonymous namespace

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

Frontmatter parse_md_frontmatter(const std::string& content) {
    Frontmatter fm;

    const auto lines = split_lines(content);
    if (lines.empty()) return fm;

    // Check opening `---`
    if (trim(lines[0]) != "---") {
        fm.body = content;
        return fm;
    }

    fm.has_frontmatter = true;

    // Find closing `---`
    std::size_t close_idx = std::string::npos;
    for (std::size_t i = 1; i < lines.size(); ++i) {
        if (trim(lines[i]) == "---") {
            close_idx = i;
            break;
        }
    }

    // Determine where the body starts (after the closing `---\n`)
    if (close_idx == std::string::npos) {
        // No closing delimiter: treat everything after line 0 as frontmatter,
        // body is empty.
        close_idx = lines.size();
    } else {
        // Reconstruct body from lines after close_idx
        std::string body;
        for (std::size_t i = close_idx + 1; i < lines.size(); ++i) {
            body += lines[i] + '\n';
        }
        fm.body = body;
    }

    // Parse frontmatter lines [1 .. close_idx)
    std::size_t i = 1;
    while (i < close_idx) {
        const std::string& line = lines[i];

        // Skip blank lines and top-level comment lines
        const std::string tl = trim(line);
        if (tl.empty() || tl[0] == '#') {
            ++i;
            continue;
        }

        std::string key, raw_val;
        if (!split_kv(line, key, raw_val)) {
            ++i;
            continue;
        }
        if (key.empty()) { ++i; continue; }

        // --- Determine the kind of value ---

        // 1. Inline list: raw_val starts with `[`
        if (!raw_val.empty() && raw_val[0] == '[') {
            fm.fields[key] = parse_inline_list(raw_val);
            ++i;
            continue;
        }

        // 2. Block scalar `|` or `>` — unsupported, store empty string
        if (raw_val == "|" || raw_val == ">") {
            fm.fields[key] = std::string{};
            // Skip continuation lines (more indented than current)
            ++i;
            while (i < close_idx) {
                const std::string& next = lines[i];
                if (next.empty()) { ++i; continue; }
                if (next[0] == ' ' || next[0] == '\t') { ++i; continue; }
                break;
            }
            continue;
        }

        // 3. Block list: raw_val is empty and next line(s) are list items
        if (raw_val.empty()) {
            // Peek ahead
            std::string dummy;
            std::size_t j = i + 1;
            bool found_items = false;
            while (j < close_idx) {
                const std::string& next = lines[j];
                if (trim(next).empty()) { ++j; continue; }
                std::string item_val;
                if (is_list_item(next, item_val)) {
                    found_items = true;
                    break;
                }
                break;
            }
            if (found_items) {
                std::vector<std::string> items;
                ++i; // move past the key line
                while (i < close_idx) {
                    const std::string& next = lines[i];
                    if (trim(next).empty()) { ++i; continue; }
                    std::string item_val;
                    if (is_list_item(next, item_val)) {
                        items.push_back(unquote(item_val));
                        ++i;
                    } else {
                        break; // end of block list
                    }
                }
                fm.fields[key] = std::move(items);
                continue;
            }
            // Empty value (e.g. `key:` with no list following)
            fm.fields[key] = std::string{};
            ++i;
            continue;
        }

        // 4. Plain scalar (strip trailing comment, then unquote)
        const std::string stripped = strip_comment(raw_val);
        fm.fields[key] = unquote(trim(stripped));
        ++i;
    }

    return fm;
}

} // namespace fn_md
