using System.Globalization;
using System.Text;
using ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts;
namespace ZB.MOM.WW.OtOpcUa.Driver.Sql;
///
/// Folds a set of s into the minimum number of parameterized queries
/// (design §3.6) — the driver's batching core. Pure and side-effect free: no connection, no I/O, no clock.
/// The GroupKey is the correctness contract. Two tags share a query only when every
/// field that shapes the result set is identical. Too coarse and a tag silently reads a neighbour's cell;
/// too fine and batching degenerates to one round-trip per tag. Both directions are pinned by
/// SqlGroupPlannerTests.
/// Injection boundary (design §8.1). Identifiers reach the command text through
/// and nothing else; every authored value becomes a
/// k0/w parameter — spelled with the dialect's own
/// (@k0 on SQL Server, :p_k0 on Oracle) — with the value carried in
/// . There is no interpolation of tag content into SQL anywhere in
/// this type.
/// The base names are this planner's, the spelling is the dialect's. Each plan carries the
/// markers it emitted and the matching s, so the
/// reader binds what the planner minted instead of re-deriving a provider convention — the two differ on
/// ODP.NET (:p_k0 in the text, bare p_k0 on the parameter).
///
public static class SqlGroupPlanner
{
/// The base-name prefix for a key-value model's IN-list parameters (k0…kN).
private const string KeyBaseNamePrefix = "k";
/// The single parameter base name for a wide-row model's row selector.
private const string RowSelectorBaseName = "w";
///
/// Groups by source and emits one per group.
/// Deterministic: groups appear in the input order of their first member, members keep their input
/// order within a group, and parameters keep the first-appearance order of their distinct values — so a
/// given tag list always yields byte-identical SQL.
///
/// The tags to plan. An empty sequence yields no plans.
/// Supplies identifier quoting and the provider's row-limit syntax.
/// One plan per distinct group key.
/// or is null.
///
/// A definition is missing a field its model requires (e.g. a wide-row tag with no row selector), or an
/// identifier cannot be quoted (an empty table-name part, a control character — see
/// ). rejects all of these
/// up front, so reaching one here means a caller built a definition by hand with a broken invariant.
///
///
/// A tag carries (design §5.4, deferred to P3).
/// Deliberately loud rather than skipped: silently dropping a tag would leave an authored node
/// permanently unfed with nothing in the log, and the parser makes this branch unreachable in practice.
///
public static IReadOnlyList Plan(IEnumerable tags, ISqlDialect dialect)
{
ArgumentNullException.ThrowIfNull(tags);
ArgumentNullException.ThrowIfNull(dialect);
// Ordered grouping: the dictionary decides membership, the list decides emission order.
var order = new List