#nullable enable

using System.Data;
using System.Globalization;
using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

/// <summary>
/// Closed result for the legacy aliases (8018/8032/5032) that MainForm routed
/// to one of two different scene builders after inspecting the playlist fields.
/// </summary>
public abstract record LegacyTwoColumnSceneLoadRequest(string BuilderKey);

public sealed record LegacyS5032LoadRequest(S5032SceneLoadRequest Request)
    : LegacyTwoColumnSceneLoadRequest("s5032");

public sealed record LegacyS8018LoadRequest(S8018SceneLoadRequest Request)
    : LegacyTwoColumnSceneLoadRequest("s8018");

/// <summary>
/// Converts the five legacy playlist columns into closed, scene-specific load requests.
/// The resolver mirrors MainForm's conditional s5001/s5032/s8018/s8001 dispatch and uses
/// parameterized, read-only stock-master lookups where MainForm used its cached stock tables.
/// </summary>
public sealed class LegacyParameterizedSceneRequestResolver
{
    private readonly IDataQueryExecutor _executor;
    private readonly TimeProvider _timeProvider;

    public LegacyParameterizedSceneRequestResolver(
        IDataQueryExecutor executor,
        TimeProvider? timeProvider = null)
    {
        _executor = executor ?? throw new ArgumentNullException(nameof(executor));
        _timeProvider = timeProvider ?? TimeProvider.System;
    }

    public S5001SceneLoadRequest CreateS5001Request(LegacyPlaylistEntry entry)
    {
        ArgumentNullException.ThrowIfNull(entry);
        if (entry.CutCode is not ("5001" or "N5001"))
        {
            throw new LegacySceneDataException("The selected cut is not an s5001 alias.");
        }

        var selection = RequireSelection(entry);
        var subject = RequireSelector(selection.Subject, "subject");

        if (subject == "BDI 지수" || subject == "BALTIC_DRY_INDEX")
        {
            return new S5001BalticDryIndexLoadRequest();
        }

        if (TryParseCommodity(subject, out var commodity))
        {
            return new S5001CommodityLoadRequest(commodity);
        }

        if (entry.CutCode == "N5001")
        {
            return CreateS5001DomesticStock(selection, subject);
        }

        if (subject is "지수" or "INDEX")
        {
            return new S5001DomesticIndexLoadRequest(
                ParseDomesticIndex(selection.GroupCode),
                IsExpectedIndex(selection.Subtype)
                    ? S5001IndexMode.Expected
                    : S5001IndexMode.Current);
        }

        if (selection.GroupCode is "해외지수" or "FOREIGN_INDEX")
        {
            return new S5001ForeignIndexLoadRequest(ParseForeignIndex(subject));
        }

        if (selection.GroupCode is "환율" or "EXCHANGE_RATE")
        {
            return new S5001ExchangeRateLoadRequest(ParseExchangeRate(subject));
        }

        if (selection.GroupCode is "해외업종" or "FOREIGN_INDUSTRY")
        {
            var identity = ParseForeignIndustryIdentity(selection.DataCode);
            return identity is null
                ? new S5001ForeignIndustryLoadRequest(subject)
                : new S5001ExactForeignIndustryLoadRequest(
                    subject,
                    identity.Value.InputName,
                    identity.Value.Symbol,
                    identity.Value.NationCode);
        }

        if (selection.GroupCode is "해외종목" or "FOREIGN_STOCK")
        {
            var identity = ParseForeignStockIdentity(selection.DataCode);
            return identity is null
                ? new S5001ForeignStockLoadRequest(subject)
                : new S5001ExactForeignStockLoadRequest(
                    subject,
                    identity.Value.Symbol,
                    identity.Value.NationCode);
        }

        if (selection.GroupCode.Contains("업종", StringComparison.Ordinal) ||
            selection.GroupCode.StartsWith("INDUSTRY_", StringComparison.Ordinal))
        {
            return new S5001DomesticIndustryLoadRequest(
                ParseDomesticMarket(selection.GroupCode, allowNxt: true),
                subject);
        }

        return CreateS5001DomesticStock(selection, subject);
    }

    public async Task<LegacyTwoColumnSceneLoadRequest> ResolveTwoColumnRequestAsync(
        LegacyPlaylistEntry entry,
        CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(entry);
        if (entry.CutCode is not ("8018" or "8032" or "5032"))
        {
            throw new LegacySceneDataException("The selected cut is not a two-column alias.");
        }

        var selection = RequireSelection(entry);
        var subject = RequireSelector(selection.Subject, "subject");

        // MainForm selects s8018 idx=3 before considering the futures condition.
        if (selection.GroupCode.Contains("업종", StringComparison.Ordinal) ||
            selection.GroupCode.StartsWith("INDUSTRY_", StringComparison.Ordinal))
        {
            var industries = SplitPair(subject, '-');
            var market = ParseDomesticMarket(selection.GroupCode, allowNxt: false);
            return new LegacyS8018LoadRequest(
                new S8018DomesticIndustryPairLoadRequest(
                    market,
                    industries[0],
                    industries[1]));
        }

        // MainForm selects the special s5032 builder whenever the combined subject
        // contains the Korean futures label. s5032 then normalizes futures to side two.
        if (subject.Contains("선물", StringComparison.Ordinal) ||
            subject.Contains("FUTURES", StringComparison.OrdinalIgnoreCase))
        {
            var values = SplitPair(subject, ',');
            // Keep this branch closed to the market targets that s5032 renders with
            // its index/futures column contract. The original accepted a stock name
            // here but then read the stock name as a numeric price and failed.
            var first = ParseMarketTarget(values[0]);
            var second = ParseMarketTarget(values[1]);
            var firstIsFutures = first == LegacyMarketQuoteTarget.Futures;
            var secondIsFutures = second == LegacyMarketQuoteTarget.Futures;
            if (firstIsFutures == secondIsFutures)
            {
                throw new LegacySceneDataException(
                    "The s5032 selection must contain exactly one futures item.");
            }

            return new LegacyS5032LoadRequest(
                new S5032IndexAndFuturesLoadRequest(firstIsFutures ? second : first));
        }

        var pair = SplitPair(subject, ',');
        var expected = IsExpectedPair(selection.Subtype);
        var firstIsTarget = TryParseMarketTarget(pair[0], out var firstTarget);
        var secondIsTarget = TryParseMarketTarget(pair[1], out var secondTarget);

        if (firstIsTarget && secondIsTarget)
        {
            return new LegacyS8018LoadRequest(
                new S8018MarketPairLoadRequest(
                    expected ? S8018MarketDataMode.Expected : S8018MarketDataMode.Current,
                    firstTarget,
                    secondTarget));
        }

        if (firstIsTarget != secondIsTarget)
        {
            var stockIndex = firstIsTarget ? 1 : 0;
            if (expected && pair[stockIndex].Contains("(NXT)", StringComparison.Ordinal))
            {
                throw new LegacySceneDataException(
                    "Expected stock data is unavailable for NXT selections.");
            }

            var stock = await ResolveCurrentStockAsync(
                pair[stockIndex],
                // The original mixed branch has a domestic/NXT stock column shape.
                // Its world fallback returns a different five-column shape which
                // SetDataText cannot render, so world remains a stock-pair-only case.
                allowWorld: false,
                cancellationToken).ConfigureAwait(false);
            var domesticMarket = ToDomesticMarket(stock.Market, allowNxt: !expected);
            return new LegacyS8018LoadRequest(
                new S8018MixedMarketAndStockLoadRequest(
                    stockIndex == 0 ? S8018PairSide.First : S8018PairSide.Second,
                    firstIsTarget ? firstTarget : secondTarget,
                    domesticMarket,
                    stock.StockName,
                    expected ? S8018MarketDataMode.Expected : S8018MarketDataMode.Current));
        }

        if (expected)
        {
            var first = await ResolveDomesticStockAsync(pair[0], cancellationToken)
                .ConfigureAwait(false);
            var second = await ResolveDomesticStockAsync(pair[1], cancellationToken)
                .ConfigureAwait(false);
            return new LegacyS8018LoadRequest(
                new S8018ExpectedStockPairLoadRequest(
                    first.Market,
                    first.StockName,
                    second.Market,
                    second.StockName));
        }

        var currentFirst = await ResolveCurrentStockAsync(
            pair[0],
            allowWorld: true,
            cancellationToken).ConfigureAwait(false);
        var currentSecond = await ResolveCurrentStockAsync(
            pair[1],
            allowWorld: true,
            cancellationToken).ConfigureAwait(false);
        return new LegacyS8018LoadRequest(
            new S8018CurrentStockPairLoadRequest(
                CurrentSession(),
                currentFirst,
                currentSecond));
    }

    public S8001SceneLoadRequest CreateS8001Request(LegacyPlaylistEntry entry)
    {
        ArgumentNullException.ThrowIfNull(entry);
        if (entry.CutCode is not ("8001" or "8002"))
        {
            throw new LegacySceneDataException("The selected cut is not an s8001 alias.");
        }

        var subject = RequireSelector(RequireSelection(entry).Subject, "subject");
        return subject switch
        {
            "코스피" or "KOSPI" => new S8001SceneLoadRequest(S8001Market.Kospi),
            "코스닥" or "KOSDAQ" => new S8001SceneLoadRequest(S8001Market.Kosdaq),
            _ => throw new LegacySceneDataException("The s8001 market selection is invalid.")
        };
    }

    private S5001DomesticStockLoadRequest CreateS5001DomesticStock(
        LegacySceneSelection selection,
        string subject)
    {
        var market = ParseDomesticMarket(selection.GroupCode, allowNxt: true);
        var mode = selection.Subtype switch
        {
            "예상체결가" or "EXPECTED" or "EXPECTED_OPENING" =>
                S5001DomesticStockMode.ExpectedOpening,
            "시간외단일가" or "AFTER_HOURS" or "AFTER_HOURS_SINGLE_PRICE" =>
                S5001DomesticStockMode.AfterHoursSinglePrice,
            _ => S5001DomesticStockMode.Current
        };
        var halted = mode == S5001DomesticStockMode.Current &&
                     (selection.Subtype.Contains("거래정지", StringComparison.Ordinal) ||
                      selection.Subtype == "HALTED");
        var nxt = market is LegacyDomesticEquityMarket.NxtKospi or
            LegacyDomesticEquityMarket.NxtKosdaq;
        var badge = !nxt
            ? S5001NxtBadge.None
            : _timeProvider.GetLocalNow().Hour <= 12
                ? S5001NxtBadge.PreMarket
                : S5001NxtBadge.AfterMarket;
        return new S5001DomesticStockLoadRequest(
            market,
            mode,
            badge,
            halted ? S5001StockTradingStatus.Halted : S5001StockTradingStatus.Regular,
            subject);
    }

    private async Task<S8018CurrentStockSelection> ResolveCurrentStockAsync(
        string rawName,
        bool allowWorld,
        CancellationToken cancellationToken)
    {
        var name = RequireSelector(rawName, "stock");
        var isNxt = name.Contains("(NXT)", StringComparison.Ordinal);
        var selector = isNxt
            ? RequireSelector(name.Replace("(NXT)", string.Empty, StringComparison.Ordinal), "stock")
            : name;

        if (isNxt)
        {
            S8018StockMarket? market = null;
            if (await StockExistsAsync(StockMasterKind.NxtKospi, selector, cancellationToken)
                    .ConfigureAwait(false))
            {
                market = S8018StockMarket.NxtKospi;
            }

            // Preserve MainForm's later-table precedence when names overlap.
            if (await StockExistsAsync(StockMasterKind.NxtKosdaq, selector, cancellationToken)
                    .ConfigureAwait(false))
            {
                market = S8018StockMarket.NxtKosdaq;
            }

            return market.HasValue
                ? new S8018CurrentStockSelection(market.Value, selector)
                : throw new LegacySceneDataException("The NXT stock selection was not found.");
        }

        S8018StockMarket? domestic = null;
        if (await StockExistsAsync(StockMasterKind.Kospi, selector, cancellationToken)
                .ConfigureAwait(false))
        {
            domestic = S8018StockMarket.Kospi;
        }

        if (await StockExistsAsync(StockMasterKind.Kosdaq, selector, cancellationToken)
                .ConfigureAwait(false))
        {
            domestic = S8018StockMarket.Kosdaq;
        }

        if (domestic.HasValue)
        {
            return new S8018CurrentStockSelection(domestic.Value, selector);
        }

        if (allowWorld &&
            await StockExistsAsync(StockMasterKind.World, selector, cancellationToken)
                .ConfigureAwait(false))
        {
            return new S8018CurrentStockSelection(S8018StockMarket.World, selector);
        }

        throw new LegacySceneDataException("The stock selection was not found.");
    }

    private async Task<DomesticStockSelection> ResolveDomesticStockAsync(
        string rawName,
        CancellationToken cancellationToken)
    {
        if (rawName.Contains("(NXT)", StringComparison.Ordinal))
        {
            throw new LegacySceneDataException(
                "Expected stock data is unavailable for NXT selections.");
        }

        var current = await ResolveCurrentStockAsync(
            rawName,
            allowWorld: false,
            cancellationToken).ConfigureAwait(false);
        return new DomesticStockSelection(
            ToDomesticMarket(current.Market, allowNxt: false),
            current.StockName);
    }

    private async Task<bool> StockExistsAsync(
        StockMasterKind kind,
        string stockName,
        CancellationToken cancellationToken)
    {
        var query = StockMasterQuery(kind, stockName);
        var table = await _executor.ExecuteAsync(
            query.Source,
            query.TableName,
            query.Spec,
            cancellationToken).ConfigureAwait(false);
        if (table is null || table.Columns.Count != 1 || table.Rows.Count != 1 ||
            !string.Equals(table.Columns[0].ColumnName, "MATCH_COUNT", StringComparison.OrdinalIgnoreCase) ||
            table.Rows[0][0] is null or DBNull)
        {
            throw new LegacySceneDataException("A stock-master lookup returned an invalid schema.");
        }

        try
        {
            var count = Convert.ToInt32(table.Rows[0][0], System.Globalization.CultureInfo.InvariantCulture);
            return count switch
            {
                0 => false,
                1 => true,
                _ => throw new LegacySceneDataException(
                    "A stock-master lookup returned a non-unique selection.")
            };
        }
        catch (LegacySceneDataException)
        {
            throw;
        }
        catch (Exception exception) when (exception is FormatException or InvalidCastException or OverflowException)
        {
            _ = exception;
            throw new LegacySceneDataException(
                "A stock-master lookup returned an invalid count.");
        }
    }

    private static ParameterizedSceneQuery StockMasterQuery(
        StockMasterKind kind,
        string stockName)
    {
        var parameter = new DataQueryParameter("stockName", stockName, DbType.String);
        return kind switch
        {
            StockMasterKind.Kospi => new ParameterizedSceneQuery(
                DataSourceKind.Oracle,
                "SCENE_RESOLVE_KOSPI_STOCK",
                new DataQuerySpec(
                    "SELECT COUNT(*) MATCH_COUNT FROM T_STOCK " +
                    "WHERE F_MKT_HALT = 'N' AND F_STOCK_WANNAME = :stockName",
                    [parameter])),
            StockMasterKind.Kosdaq => new ParameterizedSceneQuery(
                DataSourceKind.Oracle,
                "SCENE_RESOLVE_KOSDAQ_STOCK",
                new DataQuerySpec(
                    "SELECT COUNT(*) MATCH_COUNT FROM T_KOSDAQ_STOCK " +
                    "WHERE F_MKT_HALT = 'N' AND F_STOCK_WANNAME = :stockName",
                    [parameter])),
            StockMasterKind.NxtKospi => new ParameterizedSceneQuery(
                DataSourceKind.MariaDb,
                "SCENE_RESOLVE_NXT_KOSPI_STOCK",
                new DataQuerySpec(
                    "SELECT COUNT(*) MATCH_COUNT FROM N_STOCK " +
                    "WHERE F_STOP_GUBUN = 'N' AND F_STOCK_NAME = @stockName",
                    [parameter])),
            StockMasterKind.NxtKosdaq => new ParameterizedSceneQuery(
                DataSourceKind.MariaDb,
                "SCENE_RESOLVE_NXT_KOSDAQ_STOCK",
                new DataQuerySpec(
                    "SELECT COUNT(*) MATCH_COUNT FROM N_KOSDAQ_STOCK " +
                    "WHERE F_STOP_GUBUN = 'N' AND F_STOCK_NAME = @stockName",
                    [parameter])),
            StockMasterKind.World => new ParameterizedSceneQuery(
                DataSourceKind.Oracle,
                "SCENE_RESOLVE_WORLD_STOCK",
                new DataQuerySpec(
                    "SELECT COUNT(*) MATCH_COUNT FROM T_WORLD_IX_EQ_MASTER " +
                    "WHERE F_INPUT_NAME = :stockName " +
                    "AND F_FDTC = '1' " +
                    "AND F_NATC IN ('US', 'TW')",
                    [parameter])),
            _ => throw new LegacySceneDataException("The stock-master lookup is invalid.")
        };
    }

    /// <summary>
    /// New UC5 rows persist <c>symbol|nation|1</c>. Existing legacy rows did
    /// not carry a data code and retain their original name-only resolution.
    /// </summary>
    private static (string Symbol, string NationCode)? ParseForeignStockIdentity(string value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return null;
        }

        var parts = value.Split('|', StringSplitOptions.None);
        if (parts.Length != 3 ||
            !LegacyNamedForeignStockRestoreService.IsCanonicalBoundSymbol(parts[0]) ||
            parts[1].Length != 2 ||
            parts[1].Any(character => character is < 'A' or > 'Z') ||
            !string.Equals(parts[2], "1", StringComparison.Ordinal))
        {
            throw new LegacySceneDataException("The foreign-stock identity is invalid.");
        }

        return (parts[0], parts[1]);
    }

    /// <summary>
    /// Current UC5 industry rows persist <c>inputName|symbol|US|0</c>. Historical
    /// playlist rows have no data code and deliberately retain their fail-closed
    /// name-only lookup.
    /// </summary>
    private static (string InputName, string Symbol, string NationCode)?
        ParseForeignIndustryIdentity(string value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return null;
        }

        var parts = value.Split('|', StringSplitOptions.None);
        if (parts.Length != 4 ||
            !IsCanonicalForeignIndustryText(parts[0]) ||
            !LegacyNamedForeignStockRestoreService.IsCanonicalBoundSymbol(parts[1]) ||
            !string.Equals(parts[2], "US", StringComparison.Ordinal) ||
            !string.Equals(parts[3], "0", StringComparison.Ordinal))
        {
            throw new LegacySceneDataException("The foreign-industry identity is invalid.");
        }

        return (parts[0], parts[1], parts[2]);
    }

    private static bool IsCanonicalForeignIndustryText(string value) =>
        value.Length is >= 1 and <= 200 &&
        !string.IsNullOrWhiteSpace(value) &&
        string.Equals(value, value.Trim(), StringComparison.Ordinal) &&
        !value.Any(character =>
            char.IsControl(character) ||
            char.IsSurrogate(character) ||
            character == '\uFFFD' ||
            CharUnicodeInfo.GetUnicodeCategory(character) is
                UnicodeCategory.Format or
                UnicodeCategory.LineSeparator or
                UnicodeCategory.ParagraphSeparator);

    private S8018SessionPhase CurrentSession()
    {
        var now = _timeProvider.GetLocalNow();
        return now.TimeOfDay > new TimeSpan(15, 30, 0)
            ? S8018SessionPhase.AfterKrxClose
            : S8018SessionPhase.BeforeOrAtKrxClose;
    }

    private static LegacyDomesticEquityMarket ToDomesticMarket(
        S8018StockMarket market,
        bool allowNxt)
    {
        var result = market switch
        {
            S8018StockMarket.Kospi => LegacyDomesticEquityMarket.Kospi,
            S8018StockMarket.Kosdaq => LegacyDomesticEquityMarket.Kosdaq,
            S8018StockMarket.NxtKospi when allowNxt => LegacyDomesticEquityMarket.NxtKospi,
            S8018StockMarket.NxtKosdaq when allowNxt => LegacyDomesticEquityMarket.NxtKosdaq,
            _ => throw new LegacySceneDataException(
                "The selected scene branch supports only the permitted domestic stock markets.")
        };

        return result;
    }

    private static LegacySceneSelection RequireSelection(LegacyPlaylistEntry entry) =>
        entry.Selection ?? throw new LegacySceneDataException(
            "The selected cut requires native database lookup information.");

    private static string RequireSelector(string value, string label)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new LegacySceneDataException($"The scene {label} selection is required.");
        }

        return value.Trim();
    }

    private static string[] SplitPair(string value, char separator)
    {
        var values = value.Split(separator, StringSplitOptions.TrimEntries);
        if (values.Length != 2 || values.Any(string.IsNullOrWhiteSpace))
        {
            throw new LegacySceneDataException("The scene pair selection is invalid.");
        }

        return values;
    }

    private static LegacyDomesticEquityMarket ParseDomesticMarket(string value, bool allowNxt)
    {
        var market = value switch
        {
            "코스피" or "KOSPI" or "KOSPI_STOCK" => LegacyDomesticEquityMarket.Kospi,
            "코스닥" or "KOSDAQ" or "KOSDAQ_STOCK" => LegacyDomesticEquityMarket.Kosdaq,
            "코스피_NXT" or "NXT코스피" or "NXT_KOSPI" => LegacyDomesticEquityMarket.NxtKospi,
            "코스닥_NXT" or "NXT코스닥" or "NXT_KOSDAQ" => LegacyDomesticEquityMarket.NxtKosdaq,
            _ when value.Contains("코스피_NXT", StringComparison.Ordinal) ||
                       value.Contains("NXT_KOSPI", StringComparison.Ordinal) =>
                LegacyDomesticEquityMarket.NxtKospi,
            _ when value.Contains("코스닥_NXT", StringComparison.Ordinal) ||
                       value.Contains("NXT_KOSDAQ", StringComparison.Ordinal) =>
                LegacyDomesticEquityMarket.NxtKosdaq,
            _ when value.Contains("코스피", StringComparison.Ordinal) ||
                       value.Contains("KOSPI", StringComparison.Ordinal) =>
                LegacyDomesticEquityMarket.Kospi,
            _ when value.Contains("코스닥", StringComparison.Ordinal) ||
                       value.Contains("KOSDAQ", StringComparison.Ordinal) =>
                LegacyDomesticEquityMarket.Kosdaq,
            _ => throw new LegacySceneDataException("The domestic equity market selection is invalid.")
        };

        if (!allowNxt && market is LegacyDomesticEquityMarket.NxtKospi or LegacyDomesticEquityMarket.NxtKosdaq)
        {
            throw new LegacySceneDataException("The selected scene branch does not support NXT.");
        }

        return market;
    }

    private static S5001DomesticIndexTarget ParseDomesticIndex(string value) => value switch
    {
        "코스피" or "KOSPI" => S5001DomesticIndexTarget.Kospi,
        "코스닥" or "KOSDAQ" => S5001DomesticIndexTarget.Kosdaq,
        "코스피200" or "코스피 200" or "KOSPI200" => S5001DomesticIndexTarget.Kospi200,
        "선물" or "FUTURES" => S5001DomesticIndexTarget.Futures,
        "KRX100" or "KRX 100" => S5001DomesticIndexTarget.Krx100,
        _ => throw new LegacySceneDataException("The domestic index selection is invalid.")
    };

    private static bool IsExpectedIndex(string value) => value is
        "예상지수" or "EXPECTED" or "EXPECTED_INDEX";

    private static bool IsExpectedPair(string value) => value is
        "예상체결" or "예상체결가" or "EXPECTED" or "EXPECTED_OPENING";

    private static S5001ForeignIndexTarget ParseForeignIndex(string value) => value switch
    {
        "다우" or "Dow" => S5001ForeignIndexTarget.Dow,
        "나스닥" or "Nasdaq" => S5001ForeignIndexTarget.Nasdaq,
        "S&P" or "S&P500" or "Sp500" => S5001ForeignIndexTarget.Sp500,
        "독일" or "GermanyDax" => S5001ForeignIndexTarget.GermanyDax,
        "영국" or "UnitedKingdomFtse" => S5001ForeignIndexTarget.UnitedKingdomFtse,
        "프랑스" or "FranceCac" => S5001ForeignIndexTarget.FranceCac,
        "니케이" or "Nikkei" => S5001ForeignIndexTarget.Nikkei,
        "중국 상해" or "ShanghaiComposite" => S5001ForeignIndexTarget.ShanghaiComposite,
        "홍콩 항셍" or "HangSeng" => S5001ForeignIndexTarget.HangSeng,
        "대만 가권" or "TaiwanWeighted" => S5001ForeignIndexTarget.TaiwanWeighted,
        "싱가포르 지수" or "SingaporeStraitsTimes" => S5001ForeignIndexTarget.SingaporeStraitsTimes,
        "태국 지수" or "ThailandSet" => S5001ForeignIndexTarget.ThailandSet,
        "필리핀 지수" or "PhilippinesComposite" => S5001ForeignIndexTarget.PhilippinesComposite,
        "말레이시아 지수" or "MalaysiaKlse" => S5001ForeignIndexTarget.MalaysiaKlse,
        "인도네시아 지수" or "IndonesiaComposite" => S5001ForeignIndexTarget.IndonesiaComposite,
        _ => throw new LegacySceneDataException("The foreign index selection is invalid.")
    };

    private static S5001ExchangeRateTarget ParseExchangeRate(string value) => value switch
    {
        "원달러" or "WonDollar" => S5001ExchangeRateTarget.WonDollar,
        "원엔" or "WonYen" => S5001ExchangeRateTarget.WonYen,
        "원위엔" or "원위안" or "WonYuan" => S5001ExchangeRateTarget.WonYuan,
        "원유로" or "WonEuro" => S5001ExchangeRateTarget.WonEuro,
        _ => throw new LegacySceneDataException("The exchange-rate selection is invalid.")
    };

    private static LegacyMarketQuoteTarget ParseMarketTarget(string value) =>
        TryParseMarketTarget(value, out var target)
            ? target
            : throw new LegacySceneDataException("The market item selection is invalid.");

    private static bool TryParseMarketTarget(string value, out LegacyMarketQuoteTarget target)
    {
        var text = value.Trim();
        if (Enum.TryParse<LegacyMarketQuoteTarget>(text, ignoreCase: false, out target) &&
            Enum.IsDefined(target))
        {
            return true;
        }

        if (text.Contains("코스피", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.Kospi;
        else if (text.Contains("코스닥", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.Kosdaq;
        else if (text.Contains("선물", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.Futures;
        else if (text.Contains("KRX100", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.Krx100;
        else if (text.Contains("원달러", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.WonDollar;
        else if (text.Contains("원엔", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.WonYen;
        else if (text.Contains("원위엔", StringComparison.Ordinal) || text.Contains("원위안", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.WonYuan;
        else if (text.Contains("원유로", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.WonEuro;
        else if (text.Contains("다우", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.Dow;
        else if (text.Contains("나스닥", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.Nasdaq;
        else if (text.Contains("S&P", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.Sp500;
        else if (text.Contains("독일", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.GermanyDax;
        else if (text.Contains("영국", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.UnitedKingdomFtse;
        else if (text.Contains("프랑스", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.FranceCac;
        else if (text.Contains("니케이", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.Nikkei;
        else if (text.Contains("중국 상해", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.ShanghaiComposite;
        else if (text.Contains("홍콩 항셍", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.HangSeng;
        else if (text.Contains("대만 가권", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.TaiwanWeighted;
        else if (text.Contains("싱가포르 지수", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.SingaporeStraitsTimes;
        else if (text.Contains("태국 지수", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.ThailandSet;
        else if (text.Contains("필리핀 지수", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.PhilippinesComposite;
        else if (text.Contains("말레이시아 지수", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.MalaysiaKlse;
        else if (text.Contains("인도네시아 지수", StringComparison.Ordinal)) target = LegacyMarketQuoteTarget.IndonesiaComposite;
        else
        {
            target = default;
            return false;
        }

        return true;
    }

    private static bool TryParseCommodity(string value, out S5001CommodityTarget target)
    {
        if (Enum.TryParse(value, ignoreCase: false, out target) && Enum.IsDefined(target))
        {
            return true;
        }

        return CommodityNames.TryGetValue(value, out target);
    }

    private static readonly IReadOnlyDictionary<string, S5001CommodityTarget> CommodityNames =
        new Dictionary<string, S5001CommodityTarget>(StringComparer.Ordinal)
        {
            ["소맥"] = S5001CommodityTarget.FeedWheat,
            ["옥수수"] = S5001CommodityTarget.Corn,
            ["밀"] = S5001CommodityTarget.Wheat,
            ["대두"] = S5001CommodityTarget.Soybean,
            ["콩"] = S5001CommodityTarget.Bean,
            ["현미"] = S5001CommodityTarget.BrownRice,
            ["커피"] = S5001CommodityTarget.Coffee,
            ["코코아"] = S5001CommodityTarget.Cocoa,
            ["설탕"] = S5001CommodityTarget.Sugar,
            ["원면"] = S5001CommodityTarget.RawCotton,
            ["목화(면화)"] = S5001CommodityTarget.Cotton,
            ["생우(소)"] = S5001CommodityTarget.LiveCattle,
            ["비육우"] = S5001CommodityTarget.FeederCattle,
            ["돈육(돼지)"] = S5001CommodityTarget.LeanHogs,
            ["구리"] = S5001CommodityTarget.Copper,
            ["철광석"] = S5001CommodityTarget.IronOre,
            ["니켈"] = S5001CommodityTarget.Nickel,
            ["천연가스"] = S5001CommodityTarget.NaturalGas,
            ["백금"] = S5001CommodityTarget.Platinum,
            ["팔라듐"] = S5001CommodityTarget.Palladium,
            ["납"] = S5001CommodityTarget.Lead,
            ["아연"] = S5001CommodityTarget.Zinc,
            ["주석"] = S5001CommodityTarget.Tin,
            ["국제 금"] = S5001CommodityTarget.InternationalGold,
            ["국제 은"] = S5001CommodityTarget.InternationalSilver,
            ["국내 금"] = S5001CommodityTarget.DomesticGold,
            ["국내 은"] = S5001CommodityTarget.DomesticSilver
        };

    private sealed record DomesticStockSelection(
        LegacyDomesticEquityMarket Market,
        string StockName);

    private enum StockMasterKind
    {
        Kospi,
        Kosdaq,
        NxtKospi,
        NxtKosdaq,
        World
    }
}
