using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

namespace ZB.MOM.WW.ScadaBridge.ScriptAnalysis;

/// <summary>
/// The single authoritative Roslyn compile gate. Ported from the
/// SiteRuntime <c>ScriptCompilationService.CompileCore</c>, but returns
/// diagnostic messages rather than a compiled <c>Script</c> delegate — this is
/// the design-time gate (the deploy-time validation that previously relied on
/// the FAKE substring + brace-balance check in
/// <c>TemplateEngine/Validation/ScriptCompiler.cs</c>), which needs to know
/// whether the script <em>compiles</em>, not to execute it.
/// </summary>
public static class RoslynScriptCompiler
{
    /// <summary>
    /// Parses the script as C# script source and returns syntax-error diagnostic
    /// messages (severity Error only). Empty list means the script parses.
    /// </summary>
    /// <param name="code">The C# script source to parse.</param>
    /// <returns>Error-severity parse diagnostic messages; empty if the script parses.</returns>
    public static IReadOnlyList<string> ParseDiagnostics(string code)
    {
        var tree = CSharpSyntaxTree.ParseText(
            code, new CSharpParseOptions(kind: SourceCodeKind.Script));

        return tree.GetDiagnostics()
            .Where(d => d.Severity == DiagnosticSeverity.Error)
            .Select(d => d.GetMessage())
            .ToList();
    }

    /// <summary>
    /// Compiles the script against the trust-model references and imports and
    /// returns error-severity compilation diagnostic messages. Empty list means
    /// the script compiles cleanly against <paramref name="globalsType"/>.
    /// </summary>
    /// <param name="code">The C# script source to compile.</param>
    /// <param name="globalsType">
    /// Optional globals type the script binds against — e.g.
    /// <c>ScriptCompileSurface</c> for instance/shared scripts or
    /// <c>TriggerCompileSurface</c> for trigger expressions.
    /// </param>
    /// <param name="extraReferences">Optional additional metadata references.</param>
    /// <param name="extraImports">Optional additional namespace imports.</param>
    /// <returns>Error-severity compile diagnostic messages; empty if the script compiles.</returns>
    public static IReadOnlyList<string> Compile(
        string code,
        Type? globalsType = null,
        IEnumerable<MetadataReference>? extraReferences = null,
        IEnumerable<string>? extraImports = null)
    {
        try
        {
            var references = ScriptTrustPolicy.DefaultReferences.ToList();
            if (extraReferences != null)
                references.AddRange(extraReferences);

            var imports = ScriptTrustPolicy.DefaultImports.AsEnumerable();
            if (extraImports != null)
                imports = imports.Concat(extraImports);

            var options = ScriptOptions.Default
                .WithReferences(references)
                .WithMetadataResolver(CachingScriptMetadataResolver.Instance)
                .WithImports(imports);

            var script = CSharpScript.Create<object?>(code, options, globalsType);
            var diagnostics = script.Compile();

            return diagnostics
                .Where(d => d.Severity == DiagnosticSeverity.Error)
                .Select(d => d.GetMessage())
                .ToList();
        }
        catch (Exception ex)
        {
            return [$"Compilation exception: {ex.Message}"];
        }
    }
}
