using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Gtk;
using UI = Gtk.Builder.ObjectAttribute;

/// Controller for notebook page 2 (SoundBox). Not a Gtk.Window — same
/// arrangement as SettingsWindow: it drives widgets inside the shared root.
public class SoundWindow
{
    [UI] private Label  ConditionNameLabel   = null;
    [UI] private Grid   SoundButtonGrid = null;
    [UI] private Button SoundBackButton = null;

    private const int Columns = 2;

    private readonly List<CaseDefinition> _allCases;
    private readonly List<string> _path = new List<string>();

    /// Raised when the drill-down reaches a leaf case.
    public event EventHandler<CaseDefinition> CaseSelected;

    /// Raised when Back is pressed at the top level.
    public event EventHandler BackRequested;

    public SoundWindow(Builder builder)
    {
        builder.Autoconnect(this);
        if (ConditionNameLabel == null || SoundButtonGrid == null || SoundBackButton == null)
            throw new InvalidOperationException(
                "Glade id mismatch — ConditionNameLabel=" + (ConditionNameLabel != null) +
                " SoundButtonGrid=" + (SoundButtonGrid != null) +
                " SoundBackButton=" + (SoundBackButton != null));

        // GTK3 Builder does not set the CSS name from the glade id — do it here
        // so the #ConditionNameLabel / #SoundBackButton rules actually match.
        ConditionNameLabel.Name = "ConditionNameLabel";
        SoundButtonGrid.Name    = "SoundButtonGrid";
        SoundBackButton.Name    = "SoundBackButton";

        _allCases = CaseDefinition.LoadCasesFromCsv(AppFile("cases.csv"));
        if (_allCases.Count == 0)
            Console.WriteLine("WARNING: no cases loaded — check cases.csv");

        SoundBackButton.Clicked += OnBackClicked;
    }

    /// Call every time the page becomes visible.
    public void Reset()
    {
        _path.Clear();
        Render();
    }

    /// Resolve next to the .exe, NOT the working directory.
    public static string AppFile(string relative)
    {
        string dir = Path.GetDirectoryName(
            System.Reflection.Assembly.GetExecutingAssembly().Location);
        return Path.Combine(dir, relative);
    }

    // ── one method covers all three levels ─────────────────────────────

    private void Render()
    {
        var matches = _allCases.Where(MatchesPath).ToList();

        // Distinct values one level deeper than where we currently are.
        var options = matches
            .Where(c => c.TreePath.Length > _path.Count)
            .Select(c => c.TreePath[_path.Count])
            .Distinct()
            .ToList();

        ConditionNameLabel.Text = _path.Count == 0
            ? "種別を選択"
            : string.Join(" : ", _path);

        BuildButtons(options, picked =>
        {
            _path.Add(picked);

            // Landed on a leaf? Play it and stay put.
            var leaf = _allCases.FirstOrDefault(
                c => c.TreePath.Length == _path.Count && MatchesPath(c));

            if (leaf != null)
            {
                Play(leaf);
                _path.RemoveAt(_path.Count - 1);
                return;
            }

            Render();
        });
    }

    private bool MatchesPath(CaseDefinition c)
    {
        string[] p = c.TreePath;
        if (p.Length < _path.Count) return false;
        for (int i = 0; i < _path.Count; i++)
            if (!string.Equals(p[i], _path[i], StringComparison.Ordinal)) return false;
        return true;
    }

    private void BuildButtons(IEnumerable<string> labels, Action<string> onPick)
    {
        // MUST be first. Attach() stacks; without this every Render() adds a layer
        // of live buttons whose closures corrupt _path.
        foreach (var child in SoundButtonGrid.Children)
        {
            SoundButtonGrid.Remove(child);
            child.Destroy();
        }

        var list = labels.ToList();
        if (list.Count == 0) { SoundButtonGrid.ShowAll(); return; }

        bool top     = _path.Count == 0;
        int  rows    = (list.Count + Columns - 1) / Columns;
        bool compact = !top && rows > 8;

        // Colour by branch index rather than a literal, so renaming or
        // translating the CSV's top level doesn't silently turn everything blue.
        var topLevel = _allCases
            .Where(c => c.TreePath.Length > 0)
            .Select(c => c.TreePath[0])
            .Distinct()
            .ToList();

        int i = 0;
        foreach (string text in list)
        {
            string captured = text;
            var btn = new Button(captured);
            btn.Hexpand = true;
            btn.Vexpand = false;

            int branchIx = topLevel.IndexOf(top ? captured : _path[0]);

            var ctx = btn.StyleContext;
            ctx.AddClass("sound-clip");
            if (top)          ctx.AddClass("top");
            else if (compact) ctx.AddClass("compact");
            if (branchIx == 0)      ctx.AddClass("lung");
            else if (branchIx == 1) ctx.AddClass("heart");

            btn.Clicked += (s, e) => onPick(captured);

            // column-major: down the left column, then down the right
            SoundButtonGrid.Attach(btn, i / rows, i % rows, 1, 1);
            i++;
        }

        SoundButtonGrid.ShowAll();
    }

    private void Play(CaseDefinition c)
    {
        if (string.IsNullOrWhiteSpace(c.SoundPath))
        {
            Console.WriteLine("No Sound_File for case " + c.Number);
            return;
        }

        WavePlayer.Stop();   // the map page owns audio from here on

        var h = CaseSelected;
        if (h != null) h(this, c);

        // string full = AppFile(c.SoundPath);      // "sound/SND200.wav" -> absolute
        // if (!File.Exists(full))
        // {
        //     Console.WriteLine("Sound file missing: " + full);
        //     return;
        // }

        // ConditionNameLabel.Text = string.Join(" : ", _path);
        // WavePlayer.Play(full);
    }

    private void OnBackClicked(object sender, EventArgs e)
    {
        if (_path.Count > 0)
        {
            _path.RemoveAt(_path.Count - 1);
            Render();
        }
        else
        {
            WavePlayer.Stop();
            if (BackRequested != null) BackRequested(this, EventArgs.Empty);
        }
    }
}