<commit_before>##= require_self
##= require ./query/Result
##= require ./query/ArrayResult
##= require ./query/CollectionResult

class Field extends Lanes.Models.Base
    pubsub: false
    constructor: (attributes) ->
        super( _.defaults( attributes, {
            title: _.titleize(_.humanize(attributes.id))
        }))

    session:
        id:         type: 'string'
        title:      type: 'string'
        selected:   type: 'boolean'
        visible:    type: 'boolean', default: true
        query:      type: 'boolean', default: true
        editable:   type: 'boolean', default: true
        sortable:   type: 'boolean', default: true
        format:     type: 'function'
        flex:       type: 'number',  default: 1
        fixedWidth: type: 'number'
        textAlign:  type: 'string', default: 'left'
        sortBy:     type: 'any'
        component:  type: 'function'
        onColumnClick: type: 'function'

    derived:
        default_value: deps: ['id'], fn: ->
            switch @model_field.type
                when 'integer' then 0
                else ''
        model_field:
            deps: ['id'], fn: ->
                this.collection.query.model::_definition[@id]
        type:
            deps: ['model_field'], fn: ->
                type = @model_field?.type || 'string'
                if type == "code" then "string" else type
        fetchIndex:
            fn: ->
                fetchIndex = 0
                for field, index in this.collection.models
                    return fetchIndex if this == field
                    fetchIndex += 1 if field.query

    validValue: (value) ->
        if this.type == 'n'
            ! _.isNaN( parseFloat(value) )
        else
            value


class AvailableFields extends Lanes.Models.Collection

    model: Field

    constructor: (models, options) ->
        @query = options.query
        this.on('change', (changing) ->
            return unless changing.selected
            for model in @models
                model.selected = false unless model == changing
        )
        super
        @visible = @subcollection(where: {visible: true})


class Operator extends Lanes.Models.Base
    pubsub: false

    session:
        id:       'string'
        name:     'string'
        types:    'array'
        field:    'object'
        selected: 'boolean'

    derived:
        valid:
            deps: ['types', 'field']
            fn: ->
                !this.types || ( this.field && _.includes(this.types, this.field.type) )


class Operators extends Lanes.Models.Collection
    model: Operator

    constructor: ->
        super
        this.add([
            { id: 'like', name: 'Starts With', types: Lanes.Models.Query.LIKE_QUERY_TYPES }
            { id: 'eq',   name: 'Equals' }
            { id: 'lt',   name: 'Less Than', types: Lanes.Models.Query.LESS_THAN_QUERY_TYPES }
            { id: 'gt',   name: 'More Than', types: Lanes.Models.Query.LESS_THAN_QUERY_TYPES }
        ])
        this.on('change', (changing) ->
            return unless changing.selected
            for model in @models
                model.selected = false unless model == changing
        )
    setField: (field) ->
        this.invoke('set', 'field', field)
        selected = this.findWhere(selected: true)
        if selected && !selected.valid
            selected.selected = false
            this.findWhere(valid:true).selected = true



class Clause extends Lanes.Models.Base
    pubsub: false

    session:
        value     : { type: 'string', default: '' }
        field     : 'state'
        operator  : 'state'

    associations:
        operators : { collection: Operators }
        fields:
            collection: AvailableFields, options: ->
                query: this.collection.query

    derived:
        description:
            deps: ['field', 'operator']
            fn: -> "#{@field.title} #{@operator?.id}"

        isValid:
            deps: ['field', 'operator', 'value']
            fn: -> this.field.validValue(@value)

        query:
            fn: -> @collection.query

    constructor: (options) ->
        super
        this.fields.reset(options.available_fields.models)
        @operators.field = @fields.first()
        @fields.on('change:selected',    this.setField,    this)
        @operators.on('change:selected', this.setOperator, this)
        field = @fields.findWhere(visible: true)
        if field
            field.selected = true
            @setField(field)
            operator = @operators.find (o) -> o.valid
            operator?.selected = true

    setFromView: (type, val) ->
        if type == "fields" || type == "operators"
            this[type].get(val).selected = true
        else
            super

    setField: (field) ->
        return unless field.selected
        @operators.setField( field )
        this.field = field

    setOperator: (operator) ->
        return unless operator.selected
        this.operator = operator

    remove: ->
        @collection.remove(this)

    toParam: ->
        param = {}
        op = this.operator.id
        value = this.get('value')
        value += '%' if 'like' == op
        value = parseFloat(value) if @field.type == "n"
        param[ this.field.id ] = if 'eq' == op then value else { op: op, value: value }
        param


class Clauses extends Lanes.Models.Collection

    model: Clause

    initialize:(models, options) ->
        super
        @query = options.query
        @fields = options.query.fields

# needs to inherit from Base so network events will be listened to
class Lanes.Models.Query extends Lanes.Models.Base
    pubsub: false

    @LIKE_QUERY_TYPES: ['string', 'code']
    @LESS_THAN_QUERY_TYPES: ['integer', 'bigdec', 'number']
    @GREATER_THAN_QUERY_TYPES: ['integer', 'bigdec', 'number']

    @mergedSyncOptions: (args...) ->
        _.merge {}, args..., (a, b) ->
            return a.concat(b) if _.isArray(a)

    session:
        src:     'any'
        fields:  'collection'
        clauses: 'collection'
        initialField: 'state'
        idIndex: 'number'
        initialFieldIndex: 'number'
        pageSize: { type: 'number', default: 100 }
        syncOptions: 'any'
        autoRetrieve: {type:'boolean', default: false, required: true}
        title: { type: 'string', default: 'Find Record' }
        sortField: 'object'
        defaultSort: 'any'
        changeCount: {type: 'integer', default: 0}
        sortAscending: ['boolean', true, true]

    derived:
        isCollection:
            deps: ['src'], fn: -> Lanes.u.isCollection(@src)

        model:
            deps: ['src'], fn: ->
                return null unless @src
                if @isCollection then @src.model else @src

        idAttribute:
            deps: ['model'], fn: -> @model::idAttribute

        results:
            deps: ['src'], fn: ->
                if @isCollection
                    new Lanes.Models.Query.CollectionResult(this)
                else
                    new Lanes.Models.Query.ArrayResult(this, pageSize: @pageSize)

    events:
        'change:sortField':     '_updateSort'
        'change:sortAscending': '_updateSort'

    constructor: (options = {}) ->
        super
        @fields = new AvailableFields([], query: this)
        for col, i in options.fields
            rec = if _.isObject(col) then col else { id: col }
            @fields.add rec
            @idIndex = i if rec.id == @idAttribute
        unless @idIndex?
            @idIndex = @fields.length
            @fields.add(id: @idAttribute)
        @clauses = new Clauses([], query: this )
        this.listenTo(@clauses, 'change remove reset', =>
            @results.reset()
            @results.ensureLoaded() if this.autoRetrieve
        )
        this.on('change:src change:results', ->
            this.trigger('load')
        )
        this.on('load', ->
            @changeCount += 1
        )

        if @initialFieldIndex
            @initialField = this.fields.at(@initialFieldIndex)

        @initialField ||= this.fields.findWhere(id: "code") ||
            this.fields.findWhere(id: "visibleId") ||
            this.fields.first()

        @reset()
        this

    reset: ->
        unless @defaultSort is false
            sort = @defaultSort or @fields.findWhere(visible: true).id
            @setSortField( @fields.findWhere(id: sort), sortAscending: true, silent: true )

        @clauses.reset([
            {query: this, available_fields: @fields, field: @initialField}
        ])

    setSortField: (field, options = {silent: false}) ->
        @set({
            sortAscending: options.sortAscending || (if @sortField is field then !@sortAscending else true)
            sortField: field
        }, options)

    ensureLoaded: -> @results.ensureLoaded()

    isValid: ->
        ! @clauses.findWhere( isValid: false )

    loadModel: (model, options = {}) ->
        _.extend(options, _.result(this, 'syncOptions'))
        model.withAssociations(options.include || [], options)

    loadSingle: (code, options = {}) ->
        options.query = {}
        options.query[ @initialField.id ] = code
        _.extend(options, _.result(this, 'syncOptions'))
        @src.fetch(options)

    defaultField: ->
        @fields.findWhere( field: @initialField )

    addNewClause: ->
        @clauses.add(query: this, available_fields: @fields, field: @initialField)

    _updateSort: _.debounce ->
        @results._updateSort()<commit_msg>pass default sort options on through to query<commit_after>##= require_self
##= require ./query/Result
##= require ./query/ArrayResult
##= require ./query/CollectionResult

class Field extends Lanes.Models.Base
    pubsub: false
    constructor: (attributes) ->
        super( _.defaults( attributes, {
            title: _.titleize(_.humanize(attributes.id))
        }))

    session:
        id:         type: 'string'
        title:      type: 'string'
        selected:   type: 'boolean'
        visible:    type: 'boolean', default: true
        query:      type: 'boolean', default: true
        editable:   type: 'boolean', default: true
        sortable:   type: 'boolean', default: true
        format:     type: 'function'
        flex:       type: 'number',  default: 1
        fixedWidth: type: 'number'
        textAlign:  type: 'string', default: 'left'
        sortBy:     type: 'any'
        component:  type: 'function'
        onColumnClick: type: 'function'

    derived:
        default_value: deps: ['id'], fn: ->
            switch @model_field.type
                when 'integer' then 0
                else ''
        model_field:
            deps: ['id'], fn: ->
                this.collection.query.model::_definition[@id]
        type:
            deps: ['model_field'], fn: ->
                type = @model_field?.type || 'string'
                if type == "code" then "string" else type
        fetchIndex:
            fn: ->
                fetchIndex = 0
                for field, index in this.collection.models
                    return fetchIndex if this == field
                    fetchIndex += 1 if field.query

    validValue: (value) ->
        if this.type == 'n'
            ! _.isNaN( parseFloat(value) )
        else
            value


class AvailableFields extends Lanes.Models.Collection

    model: Field

    constructor: (models, options) ->
        @query = options.query
        this.on('change', (changing) ->
            return unless changing.selected
            for model in @models
                model.selected = false unless model == changing
        )
        super
        @visible = @subcollection(where: {visible: true})


class Operator extends Lanes.Models.Base
    pubsub: false

    session:
        id:       'string'
        name:     'string'
        types:    'array'
        field:    'object'
        selected: 'boolean'

    derived:
        valid:
            deps: ['types', 'field']
            fn: ->
                !this.types || ( this.field && _.includes(this.types, this.field.type) )


class Operators extends Lanes.Models.Collection
    model: Operator

    constructor: ->
        super
        this.add([
            { id: 'like', name: 'Starts With', types: Lanes.Models.Query.LIKE_QUERY_TYPES }
            { id: 'eq',   name: 'Equals' }
            { id: 'lt',   name: 'Less Than', types: Lanes.Models.Query.LESS_THAN_QUERY_TYPES }
            { id: 'gt',   name: 'More Than', types: Lanes.Models.Query.LESS_THAN_QUERY_TYPES }
        ])
        this.on('change', (changing) ->
            return unless changing.selected
            for model in @models
                model.selected = false unless model == changing
        )
    setField: (field) ->
        this.invoke('set', 'field', field)
        selected = this.findWhere(selected: true)
        if selected && !selected.valid
            selected.selected = false
            this.findWhere(valid:true).selected = true



class Clause extends Lanes.Models.Base
    pubsub: false

    session:
        value     : { type: 'string', default: '' }
        field     : 'state'
        operator  : 'state'

    associations:
        operators : { collection: Operators }
        fields:
            collection: AvailableFields, options: ->
                query: this.collection.query

    derived:
        description:
            deps: ['field', 'operator']
            fn: -> "#{@field.title} #{@operator?.id}"

        isValid:
            deps: ['field', 'operator', 'value']
            fn: -> this.field.validValue(@value)

        query:
            fn: -> @collection.query

    constructor: (options) ->
        super
        this.fields.reset(options.available_fields.models)
        @operators.field = @fields.first()
        @fields.on('change:selected',    this.setField,    this)
        @operators.on('change:selected', this.setOperator, this)
        field = @fields.findWhere(visible: true)
        if field
            field.selected = true
            @setField(field)
            operator = @operators.find (o) -> o.valid
            operator?.selected = true

    setFromView: (type, val) ->
        if type == "fields" || type == "operators"
            this[type].get(val).selected = true
        else
            super

    setField: (field) ->
        return unless field.selected
        @operators.setField( field )
        this.field = field

    setOperator: (operator) ->
        return unless operator.selected
        this.operator = operator

    remove: ->
        @collection.remove(this)

    toParam: ->
        param = {}
        op = this.operator.id
        value = this.get('value')
        value += '%' if 'like' == op
        value = parseFloat(value) if @field.type == "n"
        param[ this.field.id ] = if 'eq' == op then value else { op: op, value: value }
        param


class Clauses extends Lanes.Models.Collection

    model: Clause

    initialize:(models, options) ->
        super
        @query = options.query
        @fields = options.query.fields

# needs to inherit from Base so network events will be listened to
class Lanes.Models.Query extends Lanes.Models.Base
    pubsub: false

    @LIKE_QUERY_TYPES: ['string', 'code']
    @LESS_THAN_QUERY_TYPES: ['integer', 'bigdec', 'number']
    @GREATER_THAN_QUERY_TYPES: ['integer', 'bigdec', 'number']

    @mergedSyncOptions: (args...) ->
        _.merge {}, args..., (a, b) ->
            return a.concat(b) if _.isArray(a)

    session:
        src:     'any'
        fields:  'collection'
        clauses: 'collection'
        initialField: 'state'
        idIndex: 'number'
        initialFieldIndex: 'number'
        pageSize: { type: 'number', default: 100 }
        syncOptions: 'any'
        autoRetrieve: {type:'boolean', default: false, required: true}
        title: { type: 'string', default: 'Find Record' }
        sortField: 'object'
        defaultSort: 'any'
        changeCount: {type: 'integer', default: 0}
        sortAscending: ['boolean', true, true]

    derived:
        isCollection:
            deps: ['src'], fn: -> Lanes.u.isCollection(@src)

        model:
            deps: ['src'], fn: ->
                return null unless @src
                if @isCollection then @src.model else @src

        idAttribute:
            deps: ['model'], fn: -> @model::idAttribute

        results:
            deps: ['src'], fn: ->
                if @isCollection
                    new Lanes.Models.Query.CollectionResult(this)
                else
                    new Lanes.Models.Query.ArrayResult(this, pageSize: @pageSize)

    events:
        'change:sortField':     '_updateSort'
        'change:sortAscending': '_updateSort'

    constructor: (options = {}) ->
        super
        @fields = new AvailableFields([], query: this)
        for col, i in options.fields
            rec = if _.isObject(col) then col else { id: col }
            @fields.add rec
            @idIndex = i if rec.id == @idAttribute
        unless @idIndex?
            @idIndex = @fields.length
            @fields.add(id: @idAttribute)
        @clauses = new Clauses([], query: this )
        this.listenTo(@clauses, 'change remove reset', =>
            @results.reset()
            @results.ensureLoaded() if this.autoRetrieve
        )
        this.on('change:src change:results', ->
            this.trigger('load')
        )
        this.on('load', ->
            @changeCount += 1
        )

        if @initialFieldIndex
            @initialField = this.fields.at(@initialFieldIndex)

        @initialField ||= this.fields.findWhere(id: "code") ||
            this.fields.findWhere(id: "visibleId") ||
            this.fields.first()

        @reset()
        this

    reset: ->
        unless @defaultSort is false
            sort = @defaultSort or @fields.findWhere(visible: true).id
            @setSortField( @fields.findWhere(id: sort), sortAscending: @sortAscending, silent: true )

        @clauses.reset([
            {query: this, available_fields: @fields, field: @initialField}
        ])

    setSortField: (field, options = {silent: false}) ->
        options.sortAscending ?= (if @sortField is field then !@sortAscending else true)
        @set({
            sortAscending: options.sortAscending
            sortField: field
        }, options)

    ensureLoaded: -> @results.ensureLoaded()

    isValid: ->
        ! @clauses.findWhere( isValid: false )

    loadModel: (model, options = {}) ->
        _.extend(options, _.result(this, 'syncOptions'))
        model.withAssociations(options.include || [], options)

    loadSingle: (code, options = {}) ->
        options.query = {}
        options.query[ @initialField.id ] = code
        _.extend(options, _.result(this, 'syncOptions'))
        @src.fetch(options)

    defaultField: ->
        @fields.findWhere( field: @initialField )

    addNewClause: ->
        @clauses.add(query: this, available_fields: @fields, field: @initialField)

    _updateSort: _.debounce ->
        @results._updateSort()
{'fraction_non_alphanumeric': 0.12485549132947976, 'fraction_numerical': 0.0009458749343142406, 'mean_word_length': 1.863526933493831, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 89, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6416177', 'n_tokens_mistral': 5419, 'n_tokens_neox': 5258, 'n_words': 1343}